By Phil Zoio, August 24, 2005
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).
Author's note: this article is also published on The Server Side.
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.
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:
The screen flow for the application is shown below:

You can download both the JSF version and the Tapestry version of the application.
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.
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.
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.
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.

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.
<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 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 fromFacesContext,
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 databasepublic void pageValidate(PageEvent event): allows the
page to be validated, for example, by checking that the current user is
authenticatedpublic void pageDetached(PageEvent event): allows for
release of resources used during the requestBoth 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.
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.
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:

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.
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"/> <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.
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.
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.
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.
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:
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.
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.
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.
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:
ViewHandler
implementation can be used for plugging in different display
technologies, as in the case with Facelets and Shale's Clay plug-inStateManager 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
strategiesNavigationHandler is described in
the 'Navigation' section of this articlePropertyResolver
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
mechanismsApplication
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:
Strings
and Java objectsThis 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.
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.
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.