JavaServer Faces vs Tapestry - A Head-to-Head Comparison

After several years as the leading Java Web application framework, the reign of Apache Struts appears to be drawing to an end. Indeed, the action-based model on which Struts is based is no longer regarded by many as ideal for Java Web application development. While Struts and many other Model View Controller (MVC) frameworks from the early 2000s are largely operation-centric and stateless, the frameworks emerging most strongly are component-based and event-driven.

After several years as the leading Java web application framework, the reign of Apache Struts appears to be drawing to an end. Indeed, the action-based model on which Struts is based is no longer regarded by many as ideal for Java web application development. While Struts and many other Model View Controller (MVC) frameworks from the early 2000s are largely operation-centric and stateless, the frameworks emerging most strongly are component-based and event-driven. The leading contenders in this space are the new "standard", JavaServer Faces (JSF), and Strut's cousin from the Apache Jakarta project, Tapestry.

In this article we put these frameworks head-to-head, comparing each on its merits. We rate the two on critical aspects of their design, development and runtime environments. The intention is to provide users with a basis for making informed choices about the advantages and disadvantages of each, and for deciding which to choose when embarking on a new project. Our comparisons are based on JSF 1.1 and Tapestry 3.0.3 (with occasional references to the forthcoming Tapestry 4.0 where appropriate).

Introducing the Frameworks

JSF is the new standard web application framework emerging from JSR 127 of Sun's Java Community Process (JCP). An influential figure in JSF's creation was Struts lead developer, Craig McClanahan. Leading JSF implementations include Sun's reference implementation (JSF RI), and MyFaces, which recently became an Apache project. Many leading software vendors, including Sun, Oracle and IBM, are providing design tools targeting JSF. The current version of the specification, 1.1, was issued in May 2004. JSF 1.2, currently in public review, will also be part of the forthcoming J2EE 5.0 specification. An early access version of JSF 1.2is also available from Sun's glassfish project.

While Tapestry is gaining momentum, it is not new. It began as a SourceForge project in 2000, inspired originally by Apple WebObjects, and joined Apache in 2003. Unlike JSF, Tapestry is not a specification; there is one implementation, currently at version 3.0.3. After a year and a half wait, the release of Tapestry 4.0 is imminent, is currently in beta.

JSF and Tapestry have fundamental similarities that distinguish them from standard MVC frameworks:

  • they work at a higher level of abstraction, shielding the developer from having to work directly with the Servlet API

  • they bind web display controls directly to Java object properties, which may be Strings, numeric, date and other types, defined in classes which maintain state. User interactions (for example through button clicks and links) are mapped directly to Java event handling methods in these classes

  • both JSF and Tapestry support a component-based approach to web application development, where chunks of functionality can be packaged up and reused in different contexts. Each of the frameworks ships with a set of core components, enabling the most commonly required functionality

As we'll see in the next sections, the frameworks differ greatly at an implementation level. Writing JSF applications is a very different experience to developing Tapestry applications, for most programmers.

Sidebar: The Example Application

I have created a simple example application which is the source of most of the code snippets appearing in this article. The application provides functionality for viewing and editing holidays booked by an individual, and includes:

  • a home screen listing listing holidays booked, including start date, number of days, and some descriptive information
  • a screen providing a more detailed view of any individual holiday booking
  • a screen to add new holiday booking entries

The screen flow for the application is shown below:

Example app screen flow

You can download both the JSF version and the Tapestry version of the application.

Criterion 1: Page Development

A web application is at its heart a set of dynamic dynamic templates connected to functionality encapsulated in Java code. To the casual observer, the most obvious difference between the frameworks will appear to be that JSF templates are based on JSP, while Tapestry's templates are essentially HTML based.

JSF

JSF uses JSPs as its primary display technology. Standard compliant JSF implementations must implement a set of proscribed JSP tags to represent the core components. The JSP tags bear less than a passing resemblance to HTML. Here's an example of an HTML form using JSF component tags:

<h:form>      
  <h:panelGrid columns="2" border = "1">
        <h:outputText styleClass = "label" value="No"/>

        <h:outputText value="#{holidaySession.currentHolidayBooking.holidayID}"/>
        <h:outputText styleClass = "label" value="Date"/>
        <h:outputText value="#{holidaySession.currentHolidayBooking.date}"/>

        <h:outputText styleClass = "label" value="Number of days"/>
        <h:outputText value="#{holidaySession.currentHolidayBooking.amount.value}"/>
        <h:outputText styleClass = "label" value="Description"/>

        <h:outputText value="#{holidaySession.currentHolidayBooking.description}"/>
 </h:panelGrid>
 <BR>
 <h:commandButton value="Back" action="#{holidaydetail_backing.home}" immediate = "true"/>

</h:form>
  
JSF pages can't be previewed using a standard web browser. To preview a JSF page, you either need to use a JSF design tool, or to deploy the application for real.

JSP is the primary view technology for JSF, but not the only one. An interesting alternative is described in Hans Bergsten's article, Improving JSF by Dumping JSP. Bergsten's article also describes some serious anomalies arising from mixing JSF and JSP tags. These are being fixed through a combination of specification changes in JSF 1.2 and enhancements to the JSP 2.1 expression language.


Tapestry

For most applications, Tapestry templates are simply regular HTML, decorated with Tapestry attributes. Here's an example:

<span jwcid = "@Conditional" condition = "ognl:currentHolidayBooking">  
        <p><strong>Holiday Details</strong></p>
        <table>
        <tr>
                <td class = "label">No</td>
                <td><span jwcid = "@Insert" 
                        value = "ognl:currentHolidayBooking.holidayID">1</span>
                </td>
        </tr>
        <tr>                      
            <td class = "label">Start date</td>
                <td><span jwcid = "@Insert" 
                        value = "ognl:currentHolidayBooking.date" format = "ognl:dateFormat">1</span>
                </td>
        </tr>
        <tr>                               
            <td class = "label">Number of days</td>
                <td><span jwcid = "@Insert" 
                        value = "ognl:currentHolidayBooking.amount.value">1</span>
                </td>
        </tr> 
        <tr>               
            <td class = "label">Description</td>    
                <td><span jwcid = "@Insert" 
                        value = "ognl:currentHolidayBooking.description">1</span>
                </td>
        </tr>
        </table>
</span>

The jwcid = "@componentName" attribute identifies the tag as a Tapestry tag.

Tapestry templates don't need to be HTML. Tapestry supports any markup, provided that the Tapestry tags themselves are well-formed XML, in the sense that the opening tag must be well-formed and have a corresponding well-formed closing tag. Tapestry templates are implemented in the markup language of their target environment, which would typically be HTML or WML for wireless applications. Tapestry templates can be previewed without being deployed onto a Servlet container.

The fact that JSF tags don't look like HTML makes it harder to learn to use them initially. Not that many Java programmers I know enjoy editing HTML, but at least they are generally comfortable doing it.

The purported benefit of the JSF abstraction is greater flexibility in targeting different devices with the same template markup. The cost, however, is a loss of control; your template no longer precisely expresses the output. There is also more to learn, as you need to learn a new tag library, and how this maps to HTML. Over time, the compactness of the JSF format may help you to edit your templates more quickly, although this advantage will be less relevant for users of JSF design tools.

For me the costs of the JSF approach outweigh the advantages. The same template will rarely be used for different devices except in the simplest cases. Although JSF design tools make constructing and previewing JSF templates easier, they are no direct substitute for the mature HTML design tools currently used by web designers. The JSF approach also places more reliance on Java developers for layout design, because most HTML page designers will be uncomfortable using JSF tools.

JSF users looking for an alternative to JSP have grounds for confidence that one will emerge. JSF employs an extensible architecture which allows major portions of the framework to be extended or replaced when necessary. One of the extension points is the view handler. JSF allows alternative view handlers to be plugged in using practically any display technology. A very promising view handler implementation is Facelets, a recently created java.net open source project. Facelets draws its inspiration from the Tapestry templating model, with the aim of allowing full integration with JSF without the burden of JSP. Among some advanced templating features, Facelets allows you to create Tapestry style tags such as <input id="bar" type="text" jsfc="h:inputText" value="#{foo.bar}"/>

It will be interesting to see in the coming period to what extent Facelets is adopted by developers, and influences future revisions of the JSF specification.

Tapestry's ability to blend into a standard HTML markup is its distinguishing and most valuable feature.

Criterion 2: Java Programming Model

We've mentioned that both Tapestry and JSF allow web templates to interact directly with methods and properties defined in Java classes. The form these classes take, and how instances of these classes are created and managed at runtime, is governed by the framework's programming model.

Tapestry

Each page screen in your Tapestry application typically consists of a page template (usually HTML), a Java class with properties and methods, and a page specification file used to define how template controls are bound to Java classes. There are variations around this. Tapestry has a specific mechanism for accessing HttpSession- or ServletContext-bound state; it is possible to do so directly from within a template or page specification.

Also it is possible to have a skeletal page specification with all data binding defined in the template itself. It is not possible, however, to bind display controls to instances of arbitrary classes with request scope. Request scope bindings must be to properties and methods accessible through a specific page class. The main restriction regarding the page class implementation is that it needs to subclass BasePage or AbstractPage. In other words, the classes in which you write your presentation logic inherit a lot of dependencies which ideally should be internal to the framework. Future versions of Tapestry should remove these restrictions and allow for a less coupled programming model.


JSF

In JSF there is no page specification file. Instead, there is a global configuration file, called faces-config.xml, in which as set of "managed beans" are defined. Managed beans are regular JavaBeans with properties and event listeners. Each backing bean is identified in faces-config.xml by three items of information: a String identifier, a Java class name, and a scope for the bean, which may be request, session or application. Once defined in faces-config.xml, a display control on any JSF template can reference the backing bean using the identifier. Managed beans can be configured to reference other managed beans.

Both JSF and Tapestry integrate easily with other middle tier technologies such as Spring. The JSF managed bean facility is an Inversion of Control (IoC) container in its own right. Through extensions such as the JSF-Spring, it is possible to integrate extremely closely with Spring, allowing for instance Spring bean methods to be called using JSF expressions. While Spring integration is fairly straightforward with Tapestry 3.0, its IoC capabilities are greatly enhanced in Tapestry 4.0; Tapestry 4.0 has been rebuilt on the Hivemind, a IoC framework also started by Tapestry lead Howard Lewis Ship. Spring beans can be easily injected into Tapestry application classes.

The JSF model offers more flexibility in where you put application code, because you are not limited to using the page class. You can also achieve code reuse through composition. For example, you can set up page-specific managed beans for functionality applying to individual pages, with these beans holding references to other managed beans containing functionality shared between pages.

With Tapestry you need to use class inheritance to eliminate duplicate code in the same way. The fact that the application's page class must inherit from a framework specific base class (containing lots of framework-specific state) is not ideal.

JSF also has a more intuitive mechanism for managing session and application scoped state: any page can connect to any managed bean, whether it has request, session or application scope. Tapestry limits the application to using persistent page properties or an application-defined Visit object (for session scope state), or to an application defined Global object (for application scoped state). Tapestry 4.0 removes this limitation with the introduction of Application State Objects, which appear to be similar to JSF managed beans. Tapestry 4.0 also provides a number of productivity enhancing refinements, including support for Java 5.0 annotations, reduced need for XML, and simpler application configuration.

A quirk with Tapestry is the fact that page and component classes are normally abstract. They don't have to be, but in quite a few ways the framework encourages you to make them so. Most other frameworks which employ byte code enhancement (e.g. Hibernate) don't impose this rather unnatural mechanism on the programming model.

JSF has an intuitive and flexible programming model for creating application classes that is free of the few idiosyncrasies present in Tapestry.

Criterion 3: Request Processing Life Cycle

At the heart of a web application framework is the request processing life cycle, the set of steps the framework completes and calls executed during the processing of an individual request. The request processing life cycle should be as efficient as possible but hooks in the right places for inserting custom request handling logic in an elegant way.

JSF

JSF's life cycle is implemented in a very clearly defined six step process: Restore View, Apply Request Values, Process Validations, Update Model Values, Invoke Application and Render Response. At each point after the second of the six stages - Apply Request Values - it is possible to short-circuit the process and move directly to the Render Response phase, or even to output a response directly and notify the JSF runtime that response handling is complete. Any method with access to the JSF FacesContext object, including phase listeners, event handlers, converters and validators can short-circuit the normal request handling life cycle.


Tapestry

While JSF consists of a single life cycle model, the Tapestry life cycle depends on the engine service being invoked. This is because each engine service has its own life cycle. For example, the Direct Service handles form submissions, while the Page Service can be used to simply render a page without any additional server side processing. Each engine service has a life cycle which is designed to suit the task it is trying to accomplish. For very specialized requirements, it is possible to create your own engine service with a customized life cycle.

JSF's life cycle mechanism is easier to grasp conceptually. In Tapestry's favor is the fact that the life cycle is tuned to the task at hand, allowing a more elegant solution to some problems. With JSF there may be instances where it is difficult to figure out how the life cycle mechanism is actually being used, particularly when individual components which short-circuit the standard life cycle are present.

JSF is more accessible, while the Tapestry approach is well suited to more sophisticated requirements in that allows one to refine the life cycle to the requirements of an individual operation.

Criterion 4: Navigation

In a typical web application, we're interested in two types of navigation: action-based navigation, usually resulting from submitting a form via HTTP POST, and simple page navigation, for example when clicking between links through read-only screens. With Tapestry and in particular JSF, the distinction between these is blurred somewhat. This is because both frameworks make extensive use of a postback mechanism, in which the page that renders a form or view also handles user interactions with the rendered screen. Notwithstanding this similarity, JSF and Tapestry use quite different mechanisms for moving between pages in the application.

Tapestry

When a user interacts with a Tapestry view, for example by clicking a link or submitting a form, a listener method on the server is invoked. The listener method contains logic to determine whether to navigate to a different page, and which page to navigate to. Navigation logic is defined in code, rather than in a configuration file. Navigating between pages is simply a case of activating the required page, using the IRequestCycle.activate() method. The example below shows how this is done.

Lets consider how it works with our example holiday booking application.

Tapestry example application home

The home page contains the following code

<input jwcid = "new@Submit" listener = "ognl:listeners.newSubmit" name="new_submit" type="submit" value="New" />
to bind the submit button to the newSubmit() method in the page class.

The newSubmit() method in the page class handles the transfer by calling the IRequestCycle's activate() method.

public void newSubmit(IRequestCycle cycle)
{
        cycle.activate("NewHoliday");
}

This mechanism can be used for simple page navigation (using the DirectLink component) as well as form submission. Tapestry also provides alternatives which do not involve posting back to the current form. The ExternalLink component can be used to navigate directly to another page in the application. Here, the target page needs to implement the IExternalPage interface.

Application-specific parameter values can be encoded into the URL, and received by the target page as an array of Objects (rather than String name value pairs). But this means that your activateExternalPage() implementation needs to read the encoded parameters in the correct order.

As long as the target page does not rely on HttpSession state, the ExternalLink URL can be used to bookmark pages. But the format is far from user friendly: here's an example: http://localhost:8080/tapestry/app?service=external/HolidayDetails&sp=4. Tapestry 4.0 will allow for much more user-friendly URLs, at the price of extra configuration effort.

Tapestry supports redirects in a rather clumsy way by allowing you to throw a RedirectException. It requires some effort to implement the Redirect After Post pattern in Tapestry. One approach is to use a "redirect" page which in turn passes parameters to a target page using the ExternalLink mechanism.


JSF

In JSF, you need to use a combination of code and configuration to transfer control to a different page after posting a form. Here's what's required:

  • your form control (HtmlCommandButton or HtmlCommandLink) must be bound to a managed bean action event handler. The event handler must have the form

    public String actionName();

    You may initially get confused between action event handlers and action listeners. The latter has the signature

    public void actionListenerName(FacesContext context);

    and cannot effect navigation

  • the String value returned from the action method must correspond to an entry in the faces-config.xml, which maps this value to a subsequent view. Here's an example from the JSF version of our holiday booking application:

    <navigation-rule>
            <from-view-id>/home.jsp</from-view-id>
            <navigation-case>
                    <from-outcome>newHoliday</from-outcome>
                    <to-view-id>/newholiday.jsp</to-view-id>
            </navigation-case>
            <navigation-case>
                    <from-outcome>detail</from-outcome>
                    <to-view-id>/holidaydetail.jsp</to-view-id>
            </navigation-case>
            <navigation-case>
                    <from-outcome>delete</from-outcome>
                    <to-view-id>/home.jsp</to-view-id>
            </navigation-case>
    </navigation-rule>

    You can specify navigation as a redirect rather than forward.

    Struts users will no doubt recognize the navigation-rule mechanism. Because these rules are centrally located in an XML document, it's fairly straightforward to visually represent and manipulate these using a tool.

  • As a shortcut, you can also put the navigation action straight into the page, as in <h:commandButton value="next" action="next" />

With JSF you also have an alternative mechanism for simple page links within the application. The HtmlOutputLink can be used instead of the HtmlCommandLink. Parameters are encoded as HttpServletRequest parameters. They can be retrieved from the target page as Strings via the FacesContext object. In this case, you still need to handle the String to object conversions yourself, something which you can avoid when working with Tapestry's ExternalLink.

In my view, it's a shame JSF retained Struts's mechanism for navigation. Navigation should be regarded as part of the application logic, not configuration. Defining all navigation rules in a single configuration file removed from the logic which determines this navigation is an abstraction which in my opinion serves little purpose.

JSF could also make use of a counterpart to Tapestry's ExternalLink. While the IExternalLink mechanism is not perfect, it at least allows you to preserve parameter type information, and the IExternalPage interface provides a well defined mechanism for extracting request parameters for pages loaded in this way, something which JSF lacks.

Both frameworks have quirks; my preference is for Tapestry's code-based approach. to navigation.

Criterion 5: Event Handling

Both Tapestry and JSF use event handling mechanisms to allow the application to respond to user interactions and changes in the request processing life cycle. Event listeners are the entry points into your application's functionality.

JSF

JSF event model was designed to be similar to the JavaBeans event model. The main JSF event handlers are the action methods and the action listeners, both introduced in the previous section. Note that it is possible and sometimes necessary to define both an action listener and an action method for a component instance. This is because action listeners cannot influence navigation, while action events have no access to information on the component generating the event. Some users may find this distinction a bit confusing or even contrived.

JSF also has value change events, which allow for automatic resubmission of forms when input values are modified. There is no equivalent in the current version of Tapestry.

In addition, each phase in the request handling life cycle triggers a phase event both before and after the phase occurs. Phase listeners can configured in faces-config.xml to capture and respond to phase events. The phase listeners are not connected with any particular view or page. If this information is needed, it must be retrieved from FacesContext, a slightly laborious task.


Tapestry

In the previous section we saw an example of the Tapestry workhorse event handler, which for Tapestry 3.0 are page class methods with the signature

public void methodName(IRequestCycle cycle);
  
This event handler is used both for interfacing with the service layer and for navigation. Tapestry 4.0 offers more flexibility in how the listener method is defined, for example in allowing you to define method parameters by name. The snippet <a jwcid="@DirectLink" listener="listener:handleClick" parameters="ognl:{ id, value }"> ... </a> would be used to invoke the method in the page class: public void handleClick(int id, String value) { ... }.

An advantage of Tapestry page classes is that event handling can be tied specifically to different stages of the page loading and rendering process. Methods your page class can implement include:

  • public void pageBeginRender(PageEvent event): allows page state to be configured before rendering occurs, for example, by reading data from a database
  • public void pageValidate(PageEvent event): allows the page to be validated, for example, by checking that the current user is authenticated
  • public void pageDetached(PageEvent event): allows for release of resources used during the request

Both tapestry and JSF have event models with hooks to which application functionality can be attached. Tapestry and JSF differ markedly in the way they handle request life cycle events. Having a page specific event model allows Tapestry to shine for events which need to be handled in a page-specific way, since it provides natural places for this functionality to be added. By contrast, the JSF phase listener approach is fairly ungainly for this type of processing.

On the other hand, phase listeners work better for life cycle handling functionality which needs to be shared across the application. With Tapestry, reuse of event handling logic would need to occur through a common superclass.

JSF benefits from a value change listener events. The life cycle event model works better for Tapestry when event handling logic is page-specific, and favors JSF for application-specific life cycle event handling

Criterion 6: Component State Management

A JSF or Tapestry template needs to be transformed into a component tree before it can be used. This applies in particular for rendering forms and handling form submissions. One of the fundamental differences between JSF and Tapestry is how in how they manage component state.

Tapestry

Tapestry aims for an efficient component state management mechanism by minimizing the number of times a page's component tree is loaded. When a page is requested for the first time, an instance of the page class, together with instances of all its contained components, is created. When the request has been serviced, the page instance is added to an object pool. Subsequent requests can reuse this page instance, returning it to the pool when finished. New instances of the same page will only be created if no page instance is available in the pool when the request arrives.

What makes this possible is that a Tapestry component tree is static. It cannot be modified programmatically at runtime. Whether a component displays (conditional evaluation) and how many times it displays (iteration) depend entirely on data supplied to a component. For example, the number of items displayed by a Foreach component depends on the size of the Collection passed in as an input to the component.

The downside of Tapestry's data-centric approach is that it introduces a few subtle issues into the way that form submission is handled. Tapestry has no equivalent of the Restore View phase. To recover values obtained from form submission, it re-renders the same form, populating component properties and applying data bindings in the process. However, if the model data used to re-render the form changes, the rewound form is no longer in sync with the submitted data, resulting in a StaleLinkException. This issue is probably the number one pitfall for novice Tapestry users, and surely the number one cause for complaint on the Tapestry users' mailing list. Tapestry 4.0, however, provides at least a partial solution to the problem in the form of the For and If components, which store data used for rendering in hidden fields, allowing this same data to be used to rewind the form during postback.


JSF

JSF does not specifiy exactly how component state should be managed, leaving this as an implementation detail for the view handler. The default view handlers of both MyFaces and JSF RI, however, work in a similar way. When a page is rendered, the component tree for the view being rendered is built on the fly. The application can be configured to use client or server-based component state management. If server based state management is used, the view is saved in user's session as a serialized object. This happens when the View component's JSP tag handler is executed.

When data is posted, the same set of components need to be reconstructed as were originally rendered. This is to allow the components' state to be updated with the user-supplied values, which in turn need to be converted to the correct types and validated. The component tree is reconstructed during the Restore View phase, either from serialized HttpSession data, or from a serialized object passed in from the client. MyFaces currently only holds the last view in the session, while by default the JSF RI will hold up to 15 serialized views in the session.

JSF does not suffer from the problems associated with Tapestry's rewind process, because it maintains the component state from the previous render. This greatly eases the task for the developer. The problem for JSF is likely to be one of efficiency. Building a component tree is quite an expensive task, so it is something which the framework should do sparingly. It's questionable whether JSF succeeds on this count.

Firstly, the component tree will often need to be rebuilt when a different JSP is rendered; it is not cached indefinitely. Unlike Tapestry, the JSF component tree is dynamic; components can be (and sometimes have to be) instantiated and manipulated within the application. This makes implementation of a component tree pooling scheme similar to Tapestry's a much tougher task. JSF 1.2 is heading for a compromise position, encouraging a separation of component state into tree structure (component hierarchies and parent child relationship) and tree state (component attributes as well as objects attached to components). Only tree state would need to be managed on a per session basis; structure information could be shared efficiently across application users.

Secondly, for some page requests, the Restore View phase is not necessary. If the purpose of an event interaction is simply to navigate to a new page without processing any form data, then restoring the previous view is an unnecessary drain on resources.

Don't be surprised if Tapestry applications perform better than their current JSF equivalents because of a more efficient component state management mechanism. The price, of course, is more difficulty for the developer in ensuring that the application is free of stale links caused by the Tapestry rewind mechanism.

Tapestry's efficient component state management mechanism inspires more confidence about its ability to perform and scale. For applications which don't require high performance, the rewind problem is probably one you would happily do without.

Criterion 7: Standard Components

The standard components are the well documented, out of the box components that you get for free. They are the components that you can confidently expect to be ported to future versions of the framework. Both JSF and Tapestry provide a set of standard components. By restricting yourself to using standard components, you can avoid vendor lock-in, component compatibility issues and license costs. The question is, how far can you stretch your application, how sophisticated can you make it, before having to look for alternative component sources to meet your application's needs?

Tapestry

Tapestry provide an impressive set of standard components, covering all the main HTML controls, links, as well as components for iteration and conditional inclusion of template content. With core components alone it's possible to create fairly sophisticated form-based applications which include file uploads, rollovers, calendar-based date selection, etc. Tapestry also provides a number of other "contributed" components which are not core framework components, but are still part of the official distribution, for more advanced or specialized application requirements.


JSF

The standard JSF components cover all the standard HTML controls. However, there are some fairly basic omissions. There is also no explicit iteration or conditional logic handling components. Working around the latter is fairly straightforward in that every component includes an rendered property which determines whether the component is visible.

The absence of iteration is more problematic. One workaround is to use the HtmlDataGrid component, which allows for tabular output of dynamic data. But there are limitations in the way that HtmlDataGrid can be used with other components. For example it is not possible to dynamically create a set of radio buttons, with each radio button linked to a row on the HtmlDataGrid output. In other words, a form such as the Tapestry holiday booking home page cannot be rendered using standard JSF components. Instead, we need to use links within each row of the form, as shown below:

JSF example application home

It's also not currently possible to use JSF tags within JSTL iteration or conditional blocks, as explained in Kito Mann's article in Java World. JSF 1.2 and JSP 2.1 will together bring better integration between JSF and JSTL tags, for example in allowing JSF tags to be used effectively within <c:forEach> and <c:if> tags.

Also missing from the standard set are more sophisticated components, such as the file upload and date picking components provided by Tapestry. Of course, you should have no trouble accessing these components for your JSF applications. There are already a rich array of JSF controls available from third parties, such as Oracle ADF Faces. The problem is potential incompatibility: if you use the ADF Faces components for example, you're most likely to end up replacing all of the standard components with ADF equivalents. If vendor independence is a goal, this is not desirable.

Tapestry's out of the box components provide a clear advantage over JSF in terms of the sophistication of functionality that they support. The gap is likely to narrow, and if you are prepared to make extensive use of third party components, then this should not be too much of an issue.

Tapestry provides a more advanced set of standard components, although JSF benefits from the range of third party components available.

Criterion 8: Component Development

In spite of the variety of existing components available, it probably won't be long before you find the need (or urge) to create your own components. How successful, enjoyable and productive this process is depends on the component development model of the framework you're using.

Tapestry

The first thing to note about Tapestry components is that pages are themselves components. If you're used to writing Tapestry page templates, then writing basic components only requires knowledge in a couple of new areas:

  • firstly, component textual output can be created either using a component (HTML) template (which is essentially no different from a a page template) or by writing markup code in a Java class. Using component templates, it is particularly easy to write composite components. If your component writes its own markup you will need to create a component class extending BaseComponent, and implement renderComponent(). Tapestry provides you with an IMarkupWriter implementation which makes writing out markup content relatively easy

  • secondly, unlike pages, components have parameters. Suppose for example you want to create a component which displays monetary values. This component takes three parameters: a float value, a Format object used to specify the format of the value, and the currency code. Here, the component can be built simply using two Insert component instances in a component template, with currencyCode, amount, and numberFormat as the three component parameters. This is what the component template HTML will look like:

         
    <span jwcid = "@Insert" 
            value = "ognl:currencyCode"/>&nbsp;
    
    <span jwcid = "@Insert" 
            value = "ognl:amount"
            format = "ognl:numberFormat"/>

    A page using the component simply includes attributes for each of the component parameters:

    <span jwcid = "@CurrencyAmount" currencyCode = "ognl:currencyCode" 
        amount = "ognl:amount" format = "ognl:decimalFormat"/>
    You don't need to write code to bind your parameters to page properties; Tapestry will do this for you in all but the most specialized situations.

    A useful feature is Tapestry's supports informal parameters, tag attributes not needed by the component itself but are still required for layout purposes. A good example is CSS class references. Informal parameters can be transparently rendered without the need for any component level declaration.


JSF

Compared with Tapestry, implementing a custom JSF component is a relatively demanding exercise. Unlike Tapestry, there are no similarities in the component development and page development models. Component templates are not supported, which means you need to write contained markup using Java code. JSF custom components will typically need to following:

  • an implementation of the component class, which will extend UIComponent, normally by subclassing UIComponentBase or one of its subclasses. This class may contain fields used to hold component state

  • an implementation of the JSP tag handler, which is used to set up binding between the JSP templates and component, and is usually a subclass of UIComponentTag

  • optionally, the component can use an external renderer. One of the component's main tasks is to perform encoding by generating markup and decoding it by extracting inputted values from the request. Using an external render allows these roles to be delegated, opening up the possibility of different renderers being used for the same component

The main limitation of the JSF component model is absence of built-in support for templates. All text instead needs to be generated by ResponseWriter methods, which work in the same way as Tapestry IMarkupWriter. This can be fairly labor intensive, especially when the markup text is complex or includes JavaScript. There are quite a few complexities with JSF component development. The tag handler must explicitly deal with informal parameters, since JSP custom tag contract does not support this concept. The tag implementation and component may need a fair amount of code to maintain state for instance fields, create method and value bindings, and manage child components. Much of these operations are fairly generic and can be abstracted into utility classes or component base classes. However, this involves work upfront, unless you can find an existing library or implementation to facilitate this.

Custom component development with Tapestry is relatively simple compared to JSF.

Criterion 9: Validation and Conversion

An essential feature of any web application framework is strong support for validation. Type conversion is a closely related task, in the sense that successful type conversion is often a precondition to successful validation of an inputted value. Tapestry and JSF both have built in validation frameworks, but with significant differences.

JSF

In JSF validation is a very generalized concept. Zero or more validators can be applied to any standard JSF input component, using tags such as the following:

<h:inputText required = "true" value = "#{holiday_backing.amount}" id="amountInput">
        <f:validateDoubleRange minimum = "0.1"/>
</h:inputText>

Similarly, JSF allows also you to separately specifiy either a standard or custom converter for any input component. In this case, the validation will be applied on the inputted value after conversion is successful. Here's an example:

<h:inputText required = "true" value = "#{holiday_backing.date}" id ="chooseDateInput">
        <f:convertDateTime pattern="yyyy-MM-dd"/>                 
</h:inputText>
  

JSF defines a component model for creating custom converters and validators. If validation or type conversion is unsuccessful, a component specific FacesMessage instance is is added to FacesContext. The message contains summary, detail and severity information.

Validation can also be delegated to a managed bean by adding a method binding in the validator attribute of an input tag. This mechanism is particularly useful for accomplishing form validation, where combinations of inputted values need to be evaluated to determine whether validation should succeed.

Outputting of form-level error messages is more problematic. The JSF core component set provide an HtmlMessages component, which simply outputs the summary message from all the FacesMessage instances added to the FacesContext during validation. In practice this is not very likely to be useful; you'll probably need to create an application specific mechanism for your project to handle this more acceptably.


Tapestry

Validation support is added through the IValidator interface. Unlike JSF, where validator functionality can be attached to any core input component, validation in the Tapestry 3.0 core components are limited specifically to text input fields components, namely ValidField and subclasses. For each of these, the template or page specification can be used to specify a IValidator class to be used for validation if a value is specified. If the field is required then this is set at the validator level. A fair number of IValidator implementations are provided out of the box, including validators for text, numerical fields, date fields, URLs, emails and patterns.

The scheme works well in most circumstances but there are some limitations. Validators cannot be attached to arbitrary form input components. Validation isn't supported for some core components, such as TextArea. Also, only one IValidator instance can be applied to a validating component, which makes the task of performing multiple validations somewhat problematic.

Where Tapestry in its current form comes through more strongly is in validation of the form as a whole. During validation, information about which fields have failed validation is stored in an IValidationDelegate instance. The IValidationDelegate can be subclassed to control how field labels are rendered for fields in error. The IValidationDelegate can also be used to output customized form specific error messages.

Tapestry's other major strength is support for client side validation. Unlike JSF, standard validators all have JavaScript-based client-side support.

Type conversion also works quite differently in Tapestry from JSF. For fields being validated, the validator itself is responsible for performing type conversion, a possible limitation if type conversion and validation need to be decoupled. For example, the DateValidator allows you to specify a date format for Strings to be converted to and from java.util.Date instances.

The Tapestry developers have recognized limitations in the existing architecture; Tapestry 4.0 will ship with a completely new mechanism for validation and type conversion. It will be possible to specify validators and type converters (translators) separately. It will be possible to attach multiple validators to a single component, and provide validators for a greater variety of the standard components.

Tapestry provides a polished and functional validation implementation, and in particular handles form-specific validations well. However, there are situations where its limitations are likely to be evident. In these cases, the workarounds are straightforward enough, typically requiring validation code in the page class.

JSF offers a conceptually more powerful validation and conversion framework than what's available with the current version of Tapestry. However, JSF's implementation is not as advanced in many ways: it lacks support for client side validation, form-level error display, and has fewer validator implementations available out of the box. If you use JSF, you will probably need to find third party validators and converters or write your own custom implementations.

JSF has the edge conceptually, but Tapestry compensates through client side validation support, better form error management, and more out the box validation implementations.

Criterion 10: Internationalization

A web application framework must provide support for internationalization to allow for web applications to be localized. Both Tapestry and JSF provide built in support for internationalization.

JSF

Setting up internationalization in JSF is fairly straightforward:

  • add entries in faces-config.xml indicating which locales are supported, and add corresponding localized properties files into your application's class path (under WEB-INF/classes)

  • load the resource bundle you wish to use for a particular view into a into a variable, as shown in below: <f:loadBundle basename = "ApplicationMessages" var = "bundle"/>

  • instead of using a hardcoded strings for textual output, use the <h:outputtext value = "#{bundle.messageName)"/>

  • For parameterised messages, you can use the <h:outputformat/> tag, passing extra parameters in using nested <f:param> tags

  • the locale can be detected automatically, or you can explicitly set the locale for a view by calling UIViewRoot.setLocale()


Tapestry

Localization support in Tapestry is equally straightforward

  • for each component or page which needs to be localized, add corresponding locale specific properties file containing application messages

  • use the key attribute to identify text which needs to be localized
    <span key="hello">Hello</span>

  • for text that needs to be formatted with parameters, use the insert tag: <span jwcid="@Insert" value="ognl:messages.format('holidays_left', noDaysLeft)"/>

Tapestry also allows localized versions of templates files. This makes sense when templates consist of relatively large amount of text relative to formatting.

Both Tapestry and JSF provide the necessary support to fully localize your application. Tapestry provides a more compact format, and the ability to preview even localized templates using just a web browser is a real advantage. An annoyance with Tapestry 3.0 is that message files are page or component specific, which makes it difficult to share messages which need to be used by multiple pages or components. Tapestry 4.0 will fix this.

Tapestry's advantages are a compact format and internationalized pages which can still be previewed.

Criterion 11: Testability

An increasingly important requirement for web applications is that they can be easily unit tested. The testability of an application depends directly on how free your application code is from framework specific dependencies.

JSF

In general JSF managed beans are relatively free from framework specific dependencies. There are two exceptions: firstly, event handler methods will often need to use FacesContext to extract data required for processing. Secondly, it is possible, and sometimes necessary, to bind JSF UI controls to managed beans. For example, in the home page of our example application, this is necessary to identify the data for the selected row from the HtmlDataTable component. When these kinds of dependencies are present, mock objects will aid unit testing. Otherwise, standard unit testing techniques can be used with JSF.


Tapestry

Tapestry pages are harder to test than JSF managed beans for two reasons. First, your page classes must extend a Tapestry base class. Secondly, Tapestry encourages pages and their property accessors to be declared abstract, allowing them to be placed under the control of Tapestry's runtime. This has some advantages, but ease of unit testing is not one of them. The forthcoming version of Tapestry has support for instantiating these pages and components within a unit testing environment, but for the current version, you have to create your own mechanism for doing this.

JSF managed beans are definitely easier to unit test than their Tapestry counterparts, page classes. This advantage does not really apply for JSF custom components and less still for JSF tag handlers, which have significant runtime and framework dependencies.

For testing web UIs, web integration testing (for example through HtmlUnit or Watir) is generally more valuable than than unit tests, because the latter aren't really capable of effectively mimicking user interaction. Of course, business logic should be factored out into a separately tested service layer. Nevertheless, it is still worth writing unit tests for classes with presentation logic, and important to know that you can.

Relative freedom from framework dependencies makes it easier in general to unit test JSF managed beans than Tapestry page classes.

Criterion 12: Developer Productivity

For both Tapestry and JSF, their ultimate raison d'etre has to be the proposition that they are more productive than their competitors. Both JSF and Tapestry are very productive compared to traditional MVC frameworks largely because they allow you to write applications with less code. How they measure up against each other also depends on how efficiently this code can be created and modified, and how easily and quickly errors can be detected and corrected.

Tapestry

Tapestry also offers a number of features that help productivity. The most important of these is the ability to design templates using any HTML editor, and to preview templates within a standard web browser without deploying to an application server. Other useful features include:

  • "line precise" error reporting, where template or specification errors are identified and
    pinpointed to exact locations within the application
  • an exception page which gives detailed error and environment information
  • the Tapestry inspector, which gives a fairly detailed view of the state of the application

Tapestry does not rely heavily on tools. A couple of tools are available for the Eclipse IDE, which can be quite helpful. No WYSIWYG drag and drop visual design tools exist for Tapestry in the same way as for JSF.


JSF

JSF is by design well-suited to tool support; with a good visual tool, you should be able to assemble applications fairly quickly without having to write too much code. However, the absence of a browser-based preview mechanism makes you very reliant on the tool: your productively will depend heavily on how effectively you work with it, and how effectively it works for you.

Available JSF implementations appear to be less helpful in identifying application configuration errors. They don't for example provide line precise error reporting on the original JSPs. This may be partly because the JSF implementation controls a narrower range of the technology stack than does Tapestry, where the templating mechanism is internal to the framework.

The feedback provided by the Tapestry runtime is helpful in limiting the episodes of "pulling out hair" attempting to solve a particular problem. The write/deploy/test cycle appears to be a bit slower with JSF, partly because the time taken to compile JSPs generates extra overhead not present in Tapestry applications.

Productivity with JSF will improve as tools become more sophisticated and better used. For developers preferring a code-centric approach, Tapestry is likely to remain more productive.

Criterion 13: Ease of Learning

In the same way that powerful features and the promise of greater productivity in the future will draw developers to a framework, the perception that a framework is complex or hard to learn will put many off.

Tapestry

Tapestry is reputed to have a fairly steep learning curve. Part of the reason is that it is very different from anything that most Java developers (with the exception of those familiar with Apple WebObjects) are used to. Some understanding of how the framework works is necessary to use it properly; simply treating it as a black box is likely to result in problems.

Barriers to entry are arguably raised with the imminent arrival of Tapestry 4. Tapestry 4 goes well beyond simple evolution of the framework; in many ways it is a complete overhaul of the way things work internally. The result is a more powerful, productive framework. However, additional learning is required. For example, you need to acquire at least a basic understanding of Hivemind. One problem is that documentation is still playing catch-up. A gap has opened between the newly introduced changes and features, and the documentation available for them.


JSF

While JSF is certainly more complex than Struts, it is probably conceptually simpler to understand than Tapestry. JSF has the benefit of many articles and already several books available to help new users to get accustomed with the framework. For Tapestry users there is currently only one real choice, Tapestry In Action written by Howard Lewis Ship, but this does not cover Tapestry 4.0 developments.

With all this material available, the new JSF developer will still need to take the trouble to learn a new set of JSP custom tags which are in many ways quite peculiar to JSF.

JSF has some advantages over Tapestry in terms of ease of learning; in particular greater availability of tutorial material, and a slightly simpler framework. Tapestry HTML templates, however, are easy to follow, although you'll need to invest some effort learning how to configure template components and page classes.

The JSF framework is more accessible to the average developer, although getting started with template creation is easier with Tapestry.

Criterion 14: Industry Momentum

While the main focus of this article is on technical considerations, choosing a web framework cannot be done in a vacuum; the industry momentum behind the framework, the strength of the developer community and the extent to which industry leaders are prepared to throw their weight behind a framework, are all aspects which need to be taken into consideration.

Third Party Components

Because Tapestry and JSF are component-oriented UI frameworks the availability and quality of third party components is important. JSF components are available from a wide variety of sources, from commercial vendors through to open source projects. A fairly long list is available on JSFCentral.com. The presence of third-party component sources for JSF is essential, firstly because the standard components are unlikely to meet the requirements of complex applications, and secondly because writing JSF components is not trivial. Third-party Tapestry components appear to be available almost exclusively through the open source community, and are provided in a more informal, grass roots kind of way. See the Tapestry Wiki for a list.


Specification vs Implementation

The success of non-standard open source frameworks such as Spring and Hibernate at the expense of EJB has reopened the debate on the value of standards in Enterprise Java. The value of standards increases for projects with large development teams, higher developer turnover, and for projects which use more junior developers. For such projects, JSF's position as a standard should appeal. Standardization also offers more protection from future API changes, as backward compatibility tends to be strongly enforced with JCP standards, and allows you to avoid being locked into a single implementation. Tapestry is of course not a standard, and I find it hard to imagine that it ever will be. It's more likely that Tapestry will influence the evolution of other standards, in particular JSF.


Availability of Jobs and Staff

Industry momentum is important for developers and project managers for complementary reasons; developers can find jobs and managers can find staff more easily if there is momentum behind the framework. The number of jobs requiring JSF skills is increasing, but still an order of magnitude fewer than those requiring Struts. Tapestry job postings are relatively few and far between.

With the industry support behind it, JSF has some advantages. Its position as a standard with leading vendor support makes it easier to sell to IT managers (and other developers). In spite of its active and helpful user community, going with Tapestry will put you in more lonely territory than going with JSF.

JSF's position as a standard with broad industry support will make it more attractive to many IT managers and developers.

Criterion 15: Extensibility

The extensibility of a framework is how easily you can replace or add to the parts of the framework that you don't meet your needs, without affecting other parts of the framework.

JSF

JSF was designed from the outset to be extensible. It has a number of well-defined extension points, put to use by a growing number of third party framework add-ons. Not least of these is Struts Shale, currently being built using a new and completely different architecture and code base from Struts as most developers know it. The main extension points of JSF are:

  • view handler: this part of the framework is responsible for handling the Render Response and Restore View phases of the JSF life cycle. An alternative ViewHandler implementation can be used for plugging in different display technologies, as in the case with Facelets and Shale's Clay plug-in
  • state manager: responsible for saving and restoring a view's component tree. The StateManager is actually used by the view handler for both operations, but this use is fully decoupled. The state manager mechanism allows for pluggable component state management strategies
  • navigation handler: responsible for selecting the next view to display based on the logical outcome of application actions. The behaviour of the default NavigationHandler is described in the 'Navigation' section of this article
  • expression language: custom PropertyResolver and VariableResolver implementations can be supplied to extend the JSF EL, for example, by adding new implicit variables. For JSF 1.2, expression language extensibility will be through JSP EL mechanisms
  • application and application factory: the Application and ApplicationFactory implementations can be customized to control how references to pluggable JSF objects are provided.

Applying customizations of each of these parts of JSF is simple; just register the appropriate entry in faces-config.xml. For example, configuring your JSF application to use the Facelets view handler requires the entry:

<application>
        <view-handler>
                com.sun.facelets.FaceletViewHandler
        </view-handler>    
</application>


Tapestry

The main framework extension point for Tapestry 3.0 is the application engine, a core object typically configured once for the application. Here's a flavour of what you can extend or modify by overriding the appropriate methods in your AbstractEngine subclass:

  • property source: used to determine how configuration properties are loaded
  • page source: used to provide access to tapestry pages
  • specification source: used to provide access to page and component specification objects
  • template source: used to provide access to parsed page and component templates
  • data squeezer: used to handle conversions between Strings and Java objects
  • component class enhancer: used to enhance page and component classes, in particular to provide support for page and component properties

This extensibility mechanism has been completely overhauled for Tapestry 4.0. The Tapestry 4.0 framework is now a collection of Hivemind services, many contributions to the tapestry.Infrastructure configuration point. Tapestry also exposes an tapestry.InfrastructureOverrides configuration point, specifically for the purposes of enhancing, decorating or replacing framework infrastructure services. The services that can be replaced include the ones described above, as well as many other new and existing ones. Various other parts of the framework, from persistence of properties to exception reporting, can also be extended or replaced by contributing to other Hivemind configuration points.

JSF has a simple and well-defined extensibility model which has been present from the outset, and already used by a number of framework extensions. While it is possible to extend core Tapestry 3.0 framework services, the mechanism it uses is slightly clumsy, requiring subclassing of protected methods in AbstractEngine. Tapestry 4.0 is much more elegant and powerful, allowing framework extensions to be registered via entries to a hivemodule.xml file.

A notable difference between JSF and Tapestry is that the JSF extension points are coarse-grained, while many of the Tapestry extension points are fine-grained. This has some benefits for Tapestry, but also makes it difficult to imagine how it would be possible, for example, to completely replace the display handling mechanism. Also, exposing extension points at a very low level would not be appropriate for JSF, because it would unnecessarily restrict vendor implementations, and introduce unnecessary complexity into the specification.

JSF provides a more elegant extensibility mechanism than Tapestry 3.0. Tapestry 4.0 greatly improves on this feature of the framework.

Other Issues

I've discussed a variety of technical issues grouped neatly into individual categories. The next section considers a number of other issues, which don't fit neatly into any of the other previous categories or previously used format, but are still worthy of consideration when evaluating the two frameworks.

Migration

Users of Struts and other JSP Model 2 frameworks may find the move to JSF a less radical change. Struts users, for example, can migrate their applications step-by-step using the Struts-Faces Integration library, built using a set of JSF and Struts extensions. It's possible, although harder, to make existing JSP-based applications co-exist with Tapestry.

Future Compatibility Issues

JSF adoption is complicated by the dependencies with future releases of JSP versions. Substantial work is in progress to integrate the JSF and JSP expression languages. JSF 1.1 currently uses JSF EL, an expression language created specifically for JSF. From JSP 2.1 and JSF 1.2 these specifications will use a unified expression language. The current JSF EL will be deprecated, but still supported for backward compatibility. Taking advantage of some new JSF 1.2 features, such as JSTL integration, will require upgrading to a JSP 2.1 container, probably quite a major step in some cases. Backward compatibility issues from API changes appear more likely to arise for component developers than for application developers.

The substantial changes underway with Tapestry are independent of JSP or Servlet specification changes. There are some backward incompatibilities, and in a number of other places existing mechanisms have been deprecated. Upgrading applications from Tapestry 3.0 to 4.0 will involve some effort.

Portlets

JSF has been designed from the ground up to support Portlets (JSR 168). Portlets are self-contained mini-applications running within an enclosing portal page. JSF implementations such as MyFaces provide support for portlets through minor configuration file changes. Tapestry 4.0 will support portlets for the first time, so users of the current version will need to upgrade to take advantage of this feature.

Conclusion

I've provided a fairly thorough but hopefully fair evaluation of JSF and Tapestry, comparing them head-to-head from a variety of perspectives, each relevant for web application development. The article arose from having to make a recommendation on which of the two frameworks to use in a real project. It's a task I can imagine many Java developers facing in the months to come.

There are rich rewards to be gained from learning Tapestry. Tapestry 3.0 is impressive, and Tapestry 4.0 is looking even more like the finished article. Yet in spite of its merits, Tapestry is still not for mass consumption. If Tapestry is to get the success it deserves, it needs to find a way to broaden its base, in particular by making adoption easier for the average developer, and by removing a few quirks from its programming model. It would also benefit from more regular, incremental releases, with an emphasis on keeping the level of documentation more closely aligned with the introduction of new features.

JSF is a powerful and extensible framework, and there seems little doubt that JSF will succeed. Yet in its current form it still has some rough edges. Steps aimed at fixing the JSP integration problems are in the right direction, but may take a while to materialize. In the meantime, it would be good to see the emergence of a compelling alternative to JSP templates, perhaps based on a Tapestry-style attribute-based model. Another welcome advance would be simpler component development, at least partly brought about by the introduction of templates into the component development model. At this stage, Facelets appears a promising contender for meeting these demands, and will certainly be worth observing in coming months.

Biography

Phil Zoio is an independent Java and J2EE developer and consultant based in Suffolk in the UK. His intestests include agile development techniques, Java open source frameworks, persistence and pretty much anything and everything, really. He can be contacted on [email protected].

Dig Deeper on Front-end, back-end and middle-tier frameworks