Architecture & Component Model

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Jakarta Faces 4.1 (API jakarta.faces:jakarta.faces-api 4.1.2, part of Jakarta EE 11, Java SE 17 or later), with Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4 as the two implementations the examples were run on, on Apache Tomcat 11 with Weld 6 for CDI, with the project compiled for Java 21. Faces 4.0 (Jakarta EE 10, Java SE 11 or later) is still widely deployed; every difference that matters for this page is flagged in What Changed per Version. Faces 5.0 is not released: the latest milestone on 2026-10-08 is 5.0.0-M9, and anything said about it is "upcoming", never current behaviour.

Faces is a component framework: a page is a tree of server-side objects, and everything else on this page — the servlet that receives the request, the context objects it carries, the renderers that write the markup and the factories that let you replace almost any of it — exists to build, process and render that tree. The page names every moving part, shows where it acts in the request, and builds the Bookshelf (a catalogue of books, authors and reviews) up one example at a time. Every example below was compiled and executed in a scratch Maven project on both implementations; the output shown is real output unless it says otherwise.

The Big Picture

A Faces application is an ordinary servlet web application with one extra servlet, FacesServlet. For every request that reaches it, the servlet asks a factory for a FacesContext, hands that context to a Lifecycle to process the incoming request and then to render the response, and finally releases the context. The sequence is fixed by the specification.

sequenceDiagram participant B as Browser participant S as FacesServlet participant F as FacesContextFactory participant L as Lifecycle participant V as UIViewRoot and component tree B->>S: POST /catalog.xhtml S->>F: getFacesContext(context, request, response, lifecycle) F-->>S: FacesContext S->>L: execute(facesContext) L->>V: restore the view, decode, validate, update the model, invoke the action S->>L: render(facesContext) L->>V: build or reuse the tree, encode every component V-->>B: HTML response S->>S: facesContext.release()

The next table lists the objects that appear in that picture and the rest of this page.

Table 1. The main architectural objects
Object Scope and lifetime Role

FacesServlet

One instance per web application, normally declared in web.xml

The single entry point; runs the lifecycle for every request that is mapped to it

FacesContext

One per request, created by FacesContextFactory, released at the end

Holds everything about the request being processed: the view root, messages, response writer, current phase, flags such as renderResponse and responseComplete

ExternalContext

One per FacesContext

Faces' view of the servlet (or portlet) world: request, session and application maps, parameters, headers, redirects, the flash

Application

One per web application

The factory and registry of the framework: creates components, converters, validators and behaviours, and holds the ViewHandler, NavigationHandler, ResourceHandler, StateManager, the EL resolver and the system-event subscriptions

UIViewRoot

One per view, saved and restored between requests

The root of the component tree; carries the view id, locale, render kit id and phase listeners

UIComponent tree

Rebuilt or restored on each request; saved as state

The server-side model of the page

Renderer / RenderKit

Registered once per web application

Decode request parameters into components and encode components into markup

FacesServlet

jakarta.faces.webapp.FacesServlet is a jakarta.servlet.Servlet that accepts incoming requests and passes them to the appropriate Lifecycle. The specification defines its init and service steps: init acquires the FacesContextFactory and a Lifecycle (the lifecycle id comes from the jakarta.faces.LIFECYCLE_ID servlet init-parameter, then the context-parameter of the same name, then the value DEFAULT), and service runs four steps for each request: obtain a FacesContext, call execute() on the lifecycle, call render() on the lifecycle and call release() on the context. A FacesException thrown by execute() or render() is rethrown as a ServletException.

The servlet name is free, but the mapping must use the same name. Any valid url-pattern is allowed, by prefix (/faces/*) or by extension (*.faces); the specification also describes a default mapping of the servlet to *.xhtml, which jakarta.faces.DISABLE_FACESSERVLET_TO_XHTML switches off, and Faces 4.0 added an automatic extensionless mapping. The complete mapping options, faces-config.xml and @FacesConfig belong to the project setup page of this section; this page needs only one declaration.

WEB-INF/web.xml of the Bookshelf scratch project
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
         version="6.1">
  <servlet>
    <servlet-name>facesServlet</servlet-name>
    <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>facesServlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
</web-app>

The servlet is deliberately thin. Everything it does with a request is delegated to the factories (see Factories and the Decorator Pattern) and to the Lifecycle, and the specification allows implementations to offer other ways to start the lifecycle, which is why code that depends on a particular entry point is not portable.

FacesContext

jakarta.faces.context.FacesContext represents all the contextual information associated with processing one incoming request and creating the corresponding response. The implementation creates it before the lifecycle starts, through FacesContextFactory.getFacesContext(...), and calls release() when the lifecycle is complete, so that it can be pooled. Because it is bound to the request, it is the object you reach for from almost every other Faces API.

  • FacesContext.getCurrentInstance() returns the context of the request being processed on the current thread, or null outside a Faces request.

  • In a CDI bean it can be injected: the specification lists FacesContext, ExternalContext, Flash, ResourceHandler and HttpSession among the objects valid for @Inject (Faces 2.3 and later).

  • In an EL expression the implicit object facesContext is the same object.

The members used most often group as follows.

Table 2. The FacesContext members you use daily
Member Purpose

getViewRoot(), setViewRoot(...)

The component tree of the current view

getExternalContext(), getApplication()

The servlet-world facade and the application singleton, as a convenience over the factory lookup

addMessage(clientId, message), getMessages(), getMaximumSeverity()

The message queue read by h:messages and h:message; a null client id means a global message

getCurrentPhaseId(), isPostback()

Where the request is in the lifecycle, and whether it is a postback to a previously rendered view

renderResponse(), responseComplete()

Short-circuit the lifecycle: jump to Render Response, or declare the response already written

getResponseWriter(), getRenderKit()

What renderers write into, and the render kit of the current view

getAttributes()

A map that lives exactly as long as the Faces lifecycle of the request

getPartialViewContext()

Ajax requests: which components to execute and render

The example below builds a request-scoped bean that reads from the context, the external context and the application, and a page with one button that runs it. The bean’s work is done in the action method, not in a getter, so the values are computed once and in the phase you expect.

RequestInfo.java — a CDI bean that inspects the three context objects
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.html.HtmlCommandButton;
import jakarta.faces.component.html.HtmlInputText;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.List;

@Named
@RequestScoped
public class RequestInfo {

    @Inject
    private FacesContext facesContext;

    @Inject
    private ExternalContext externalContext;

    private final List<String> lines = new ArrayList<>();

    public String describe() {
        lines.add("phase = " + facesContext.getCurrentPhaseId());
        lines.add("postback = " + facesContext.isPostback());
        lines.add("viewId = " + facesContext.getViewRoot().getViewId());
        lines.add("renderKitId = " + facesContext.getViewRoot().getRenderKitId());
        lines.add("locale = " + facesContext.getViewRoot().getLocale());
        lines.add("projectStage = " + facesContext.getApplication().getProjectStage());
        lines.add("contextPath = " + externalContext.getRequestContextPath());
        lines.add("servletPath = " + externalContext.getRequestServletPath());
        lines.add("q parameter = " + externalContext.getRequestParameterMap().get("q"));
        lines.add("request class = " + externalContext.getRequest().getClass().getSimpleName());
        lines.add("faces context class = " + facesContext.getClass().getSimpleName());
        lines.add("viewId from EL = " + facesContext.getApplication()
                .evaluateExpressionGet(facesContext, "#{facesContext.viewRoot.viewId}", String.class));
        lines.add("default events = " + new HtmlInputText().getDefaultEventName() + ", "
                + new HtmlCommandButton().getDefaultEventName());
        return null;
    }

    public List<String> getLines() {
        return lines;
    }
}
info.xhtml — the page that runs it
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets">
<h:head><title>Request info</title></h:head>
<h:body>
  <h:form id="info">
    <h:commandButton id="describe" value="Describe" action="#{requestInfo.describe}"/>
  </h:form>
  <ul>
    <ui:repeat value="#{requestInfo.lines}" var="line">
      <li><h:outputText value="#{line}"/></li>
    </ui:repeat>
  </ul>
</h:body>
</html>

Running the page and pressing the button produces the following lines (Mojarra 4.1.17, JVM started with an English locale; MyFaces 4.1.4 prints the same values except for the phase, which it prints as INVOKE_APPLICATION(5) rather than INVOKE_APPLICATION 5):

phase = INVOKE_APPLICATION 5
postback = true
viewId = /info.xhtml
renderKitId = HTML_BASIC
locale = en_US
projectStage = Production
contextPath = /bookshelf
servletPath = /info.xhtml
q parameter = null
request class = RequestFacade
faces context class = FacesContext$Proxy$_$$_WeldClientProxy
viewId from EL = /info.xhtml
default events = valueChange, action

Three details stand out. The injected FacesContext is a CDI client proxy, not the implementation class, so compare contexts with FacesContext.getCurrentInstance() if identity matters. The phase line proves the action ran in Invoke Application, which is why an action method can read the model values that Update Model Values has just written. And the last line anticipates the behaviours section: the default client event of h:inputText is valueChange and that of a command is action.

ExternalContext

jakarta.faces.context.ExternalContext exists so that Faces code does not depend on the servlet API. Most of the time the environment is a servlet container, but the abstraction also covered portlets, and it gives wrappers and tests a single seam to replace. It exposes the container objects as maps and as methods, and the specification states that none of its properties can be set through it. The ones you use:

  • the scope maps getApplicationMap(), getSessionMap() and getRequestMap(), plus getRequestParameterMap(), getRequestParameterValuesMap() and getRequestHeaderMap()

  • getRequestContextPath(), getRequestServletPath() and getRequestPathInfo() for URL work

  • getInitParameter(...) and getInitParameterMap() for web.xml context-parameters

  • redirect(url) and getFlash() for redirects that carry messages

  • getRequest(), getResponse() and getContext() as escape hatches to the raw servlet objects, which return Object precisely because the abstraction is not tied to servlets

  • addResponseCookie(...), which Faces 4.0 extended with custom cookie attributes such as SameSite

A pitfall hides in the EL implicit objects. The implicit object application is not the Faces Application; per the specification’s table of implicit objects it is externalContext.getContext(), which in a servlet container is the ServletContext. To reach the Faces Application from EL or Java, go through facesContext.application or facesContext.getApplication().

Application

jakarta.faces.application.Application is the singleton of the framework for one web application. You reach it with facesContext.getApplication(), or through ApplicationFactory in code that runs outside a request. It is both a factory and a registry.

  • Factory methods create the objects Faces is made of, by logical name: createComponent(String componentType), createComponent(FacesContext, String componentType, String rendererType) (which also assigns the renderer type), createConverter(...), createValidator(...) and createBehavior(...). The logical names come from @FacesComponent, @FacesConverter, @FacesValidator and @FacesBehavior (or faces-config.xml).

  • Pluggable services are held here: getViewHandler(), getNavigationHandler(), getResourceHandler(), getStateManager(), getFlowHandler(), getELResolver(), getExpressionFactory() and getSearchExpressionHandler(). Each can be decorated; see Factories and the Decorator Pattern.

  • Configuration facts are readable: getProjectStage(), getSupportedLocales() and getMessageBundle().

  • Event subscription is available through subscribeToEvent(...) and related methods, which attach a SystemEventListener for application-wide events such as PostConstructApplicationEvent.

  • Expression evaluation from Java is available through evaluateExpressionGet(FacesContext, String, Class).

The listing below creates a component by its component type and evaluates an expression, the two most common reasons to call the application from application code. The BadgeBean is the one developed later in The binding Attribute and Its Pitfalls; this excerpt shows only the creation step.

Creating components and evaluating an expression through the Application
Application application = facesContext.getApplication();

// a component by its registered type: the standard type constants live on the concrete classes
HtmlOutputText badge = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
badge.setValue("Classic");

// an EL expression evaluated from Java
String viewId = application.evaluateExpressionGet(facesContext, "#{facesContext.viewRoot.viewId}", String.class);

UIViewRoot

The component tree has exactly one root, an instance of jakarta.faces.component.UIViewRoot. It is the first component created for a view and the only one that is not a child of anything; facesContext.getViewRoot() returns it. It is a UIComponent like the others, so it has children and facets, but it also carries view-level properties:

  • viewId, the logical path of the page (/catalog.xhtml), and locale

  • renderKitId, the id of the render kit that renders the view; the default ViewHandler calls calculateRenderKitId() and stores the result here, which is how an application switches render kit per view

  • phase listeners, addPhaseListener(...) and the beforePhase / afterPhase method expressions, which the <f:view> tag exposes to page authors

  • getViewMap(), the map behind @ViewScoped beans

  • createUniqueId(), the source of the generated j_id... identifiers

  • getDoctype() (Faces 4.0 and later) and the component resources collection used for h:head resources

  • the metadata facet, where f:metadata places the view parameters of a bookmarkable page

UIViewRoot is not a naming container: its descendants' client ids are not prefixed with the root’s own id, which is why the form in the tree later on is search and not j_id1:search. Its id is generated, j_id1 on Mojarra and j_id__v_0 on MyFaces, so it should never be written into scripts or tests.

The <f:view> tag configures the view root from the page. It is optional, and Facelets creates a root anyway; use it when you need the locale, the render kit or the phase listeners to be set from the markup.

The attributes that map to UIViewRoot properties are locale, renderKitId, contentType, encoding, transient, beforePhase and afterPhase. The page below sets two of them and reuses the RequestInfo bean from the previous section to prove it.

view.xhtml — f:view sets the locale and the render kit of the view root
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core"
      xmlns:ui="jakarta.faces.facelets">
<f:view locale="fr" renderKitId="HTML_BASIC">
  <h:head><title>View root</title></h:head>
  <h:body>
    <h:form id="view">
      <h:commandButton id="describe" value="Describe" action="#{requestInfo.describe}"/>
    </h:form>
    <ul>
      <ui:repeat value="#{requestInfo.lines}" var="line">
        <li><h:outputText value="#{line}"/></li>
      </ui:repeat>
    </ul>
  </h:body>
</f:view>
</html>

Pressing the button prints locale = fr and renderKitId = HTML_BASIC in the same listing as before, on both implementations.

The Component Tree

When a request asks for a Facelets view, Faces builds a tree of UIComponent objects from the page: the tree is the runtime form of the page. Each tag in the page that stands for a component (h:form, h:inputText, h:dataTable) creates one node, and the nodes are linked by parent and child references. The tree is what the lifecycle walks: every phase is a recursive call down the tree. The figure shows the tree of the Bookshelf catalogue page on the left and the HTML its renderers wrote on the right; the numbered markers pair each component with its markup.

The component tree of a Bookshelf catalogue page, with facets and naming containers, next to the HTML written by its renderers

How the tree comes to exist depends on the request. On an initial request the view is built from the page during Render Response. On a postback the tree is restored in Restore View and then processed. With partial state saving, which is the mechanism the specification keeps (full state saving is deprecated since 4.1, see What Changed per Version), the implementations rebuild the tree from the view definition and apply the saved state to it, which is why changes you make to the tree after it is built must be made in ways Faces can track.

UIComponent and UIComponentBase

jakarta.faces.component.UIComponent is the abstract base class of every component, and UIComponentBase supplies default implementations of nearly every method, so component writers extend UIComponentBase or one of its concrete subclasses. The properties every component has:

Table 3. The structural properties of UIComponent
Member Meaning

getId(), setId(...)

The component identifier: starts with a letter, then letters, digits, dashes or underscores; unique within the closest ancestor naming container

getClientId(FacesContext)

The identifier used in the markup and in request parameters, derived from the id and the enclosing naming containers; see Naming Containers and Client Identifiers

getFamily()

The component family: a string shared by components that a renderer can treat alike; see Component Families and the Standard Classes

getRendererType(), setRendererType(...)

The key of the renderer, within the family, that decodes and encodes this component; null means the component renders itself

getParent(), getChildren()

The tree structure; the children list is mutable and keeps the parent links up to date

getFacets(), getFacet(name)

Named subordinate components, outside the child list; see Facets

getAttributes()

A map that reads and writes bean properties of the component and, for names that are not properties, a generic attribute store; this is what the tag attributes end up in

getValueExpression(name), setValueExpression(name, expr)

The value expression behind a property such as value or rendered; the special name binding is described in The binding Attribute and Its Pitfalls

getPassThroughAttributes()

Attributes the renderer writes unchanged into the markup (HTML5 friendly markup)

isRendered(), isTransient()

Whether the component takes part in rendering and processing, and whether it is left out of saved state

getStateHelper()

The helper that stores component properties so that they are saved and restored with the view

Beyond structure, UIComponentBase implements the processing methods the lifecycle calls on every node: processDecodes, processValidators and processUpdates walk the subtree and call decode(), validate() and updateModel() on the components that have them; broadcast(FacesEvent) delivers queued events; and encodeBegin(), encodeChildren() and encodeEnd() (driven by encodeAll()) write the markup. The lifecycle page of this section shows when each is invoked.

Two navigation helpers matter in practice. findComponent(expr) searches by id using the naming-container rules of Naming Containers and Client Identifiers, and visitTree(VisitContext, VisitCallback) visits the subtree with the visit API that Faces 2.0 introduced, handling iterating components correctly. The tree inspector below uses both.

Property Values, ValueExpressions and the StateHelper

A tag attribute such as rendered="#{bookBean.admin}" or max="#{shelfBean.maxStars}" does not copy a value into the component. Facelets stores the expression itself, with setValueExpression(name, expr), and the component evaluates it each time the property is read. getValueExpression(name) returns the stored expression or null, and a literal attribute (max="7") is set through the property setter instead. The standard properties follow one convention: the getter asks the StateHelper for the property, and StateHelper.eval(key) returns the value stored with put(key, value) or, when none was stored, evaluates the ValueExpression registered under the same name; eval(key, defaultValue) and, since Faces 4.0, eval(key, Supplier) give the fallback when neither exists. A property written this way is therefore settable both from Java and from a page, and the expression is evaluated again on every read, which is why the same attribute can show a different value in the next request.

The members that make this work are interfaces every component implements through UIComponent: PartialStateHolder (markInitialState(), initialStateMarked(), clearInitialState()) tells the component to report only what changed since the view was built, the basis of partial state saving; TransientStateHolder keeps values that must never be saved; and SystemEventListenerHolder (getListenersForEventClass) is what lets a component be the source of a SystemEvent and keep the listeners subscribed to it with subscribeToEvent. The state methods are covered in the state saving page, the system events in events and listeners.

The class below was compiled against the 4.1.2 API but, unlike the other examples of this page, was not run.

PropertyEvaluation.java — an expression or a literal behind the same property
package org.example.bookshelf;

import jakarta.el.ValueExpression;
import jakarta.faces.application.Application;
import jakarta.faces.component.UIInput;
import jakarta.faces.context.FacesContext;

public class PropertyEvaluation {

    /** The component of the example below; {@code getMax} reads the StateHelper, then the expression. */
    public static class Stars extends UIInput {
        public int getMax() {
            return (Integer) getStateHelper().eval("max", 5);
        }

        public void setMax(int max) {
            getStateHelper().put("max", max);
        }
    }

    public static void configure(FacesContext context, Stars stars) {
        Application application = context.getApplication();
        ValueExpression expression = application.getExpressionFactory().createValueExpression(
                context.getELContext(), "#{shelfBean.maxStars}", Integer.class);

        // what <bk:rating max="#{shelfBean.maxStars}"/> does: store the expression, not a value
        stars.setValueExpression("max", expression);
        stars.getMax();                      // evaluates #{shelfBean.maxStars} now

        // what <bk:rating max="7"/> does: store a literal in the StateHelper
        stars.setValueExpression("max", null);
        stars.setMax(7);
        stars.getMax();                      // 7
    }
}

Component Families and the Standard Classes

Faces ships a set of component classes whose roles are fixed by the specification. They form a small hierarchy, and a few behavioural interfaces describe what each class can do.

classDiagram UIComponent <|-- UIComponentBase UIComponentBase <|-- UIOutput UIOutput <|-- UIInput UIOutput <|-- UIOutcomeTarget UIInput <|-- UISelectOne UIInput <|-- UISelectMany UIInput <|-- UISelectBoolean UIInput <|-- UIViewParameter UIComponentBase <|-- UICommand UIComponentBase <|-- UIData UIComponentBase <|-- UIForm UIComponentBase <|-- UIViewRoot UIComponentBase <|-- UIPanel UIComponentBase <|-- UIColumn UIComponentBase <|-- UINamingContainer class ValueHolder { <<interface>> } class EditableValueHolder { <<interface>> } class ActionSource { <<interface>> } class NamingContainer { <<interface>> } ValueHolder <|.. UIOutput EditableValueHolder <|.. UIInput ActionSource <|.. UICommand NamingContainer <|.. UIForm NamingContainer <|.. UIData NamingContainer <|.. UINamingContainer

The roles of the classes you meet daily:

  • UIOutput displays a value; UIInput (a subclass) takes input; UISelectOne, UISelectMany and UISelectBoolean are inputs that choose; UIViewParameter is an input that maps a query parameter to the model

  • UICommand is a control that fires an action when activated, and UIOutcomeTarget renders a navigation link or button (h:link, h:button)

  • UIData binds to a collection through a jakarta.faces.model.DataModel and renders its UIColumn children once per row; UIForm is the input form; UIPanel lays out children; UIGraphic shows an image

  • UIMessage and UIMessages display queued messages; UIParameter, UISelectItem and UISelectItems carry data for their parents

  • UIViewRoot is the root; UINamingContainer is the plain naming container used for composite components

A component type is the registered logical name used by Application.createComponent (jakarta.faces.HtmlInputText for the concrete HTML class), and a component family groups components for renderer lookup. The concrete classes in jakarta.faces.component.html (HtmlInputText, HtmlCommandButton, HtmlDataTable, …​) extend the abstract classes and add strongly typed properties for the renderer-dependent attributes. Their family and renderer type decide which Renderer is used. Reading these from a running tree (the inspector in Inspecting a Tree at Run Time) gives the pairs behind the tags used in this section:

Table 4. Tags, component classes, families and renderer types as read from a running tree
Tag Component class Family Renderer type

h:form

HtmlForm

jakarta.faces.Form

jakarta.faces.Form

h:inputText

HtmlInputText

jakarta.faces.Input

jakarta.faces.Text

h:commandButton

HtmlCommandButton

jakarta.faces.Command

jakarta.faces.Button

h:outputText

HtmlOutputText

jakarta.faces.Output

jakarta.faces.Text

h:outputLabel

HtmlOutputLabel

jakarta.faces.Output

jakarta.faces.Label

h:dataTable

HtmlDataTable

jakarta.faces.Data

jakarta.faces.Table

h:column

HtmlColumn

jakarta.faces.Column

none: the table renderer draws the cells

h:body

HtmlBody

jakarta.faces.Output

jakarta.faces.Body

The family-plus-renderer-type pair is exactly the key of RenderKit.getRenderer(family, rendererType). A h:commandButton and an h:commandLink are both UICommand components in the jakarta.faces.Command family; only the renderer type differs, which is the whole component and renderer split described in Renderers and Render Kits.

Facets

A facet is a named subordinate component that plays a specific role for its owner, independent of the parent and child relationship. A data table has its rows (the children, here the columns) and also a header and a footer, which are not rows. Facets are not in getChildren(); they are in the map getFacets(), keyed by their role name. They still take part in the lifecycle methods, so a facet that contains an input is decoded and validated like any child, and getFacetsAndChildren() iterates both.

In a page, <f:facet name="..."> registers the facet on the closest parent component tag. When the facet contains more than one child component, Faces wraps them in a UIPanel so that the facet is still a single component. The standard facet names are defined by the renderer: header and footer for tables and columns, caption for tables, and Faces itself uses facets such as the metadata facet of the view root and the component resource containers in h:head.

In the figure, the two facet "header" nodes are the markup <f:facet name="header">Catalogue</f:facet> and the column header. The inspector listing below prints them as facet header. A custom component exposes a facet with typed accessors over getFacet(name) and getFacets().put(name, component).

A component with a facet, created from Java
HtmlDataTable table = (HtmlDataTable) application.createComponent(HtmlDataTable.COMPONENT_TYPE);
HtmlOutputText caption = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
caption.setValue("Catalogue");
table.getFacets().put("header", caption);   // not table.getChildren().add(caption)

Naming Containers and Client Identifiers

Two components in different forms may both be called title, and a component in a table is rendered once per row, so the id in the markup cannot be the component id. The client identifier is derived from the component id and the client identifiers of the enclosing naming containers. NamingContainer is a marker interface: all components below a naming container with a non-null id must have ids unique within that container. Among the standard components UIForm and UIData implement it, and UINamingContainer is the container used for composite components. The client id is the container’s client id, a separator and the component id; the separator constant is NamingContainer.SEPARATOR_CHAR, the colon, which the specification calls out as legal in HTML id and name attributes. The context-parameter jakarta.faces.SEPARATOR_CHAR overrides it, and component code must call UINamingContainer.getSeparatorChar(FacesContext) rather than the constant.

The rules, all confirmed by running the page below:

  • A component outside any form or table has a client id equal to its id.

  • Inside <h:form id="search">, an input with id query has client id search:query.

  • A UIData adds the row index: the output title inside <h:dataTable id="books"> renders as books:0:title, books:1:title, and so on, one id per row. Outside iteration the same component’s client id is books:title.

  • <h:form prependId="false"> stops that form from prefixing its descendants' ids, so the note input of a form with that setting renders as plain note.

  • A component without an id receives a generated one, from UIViewRoot.createUniqueId(), such as j_idt5 on Mojarra. Generated ids are implementation specific and change when the page changes.

  • The for attribute of h:outputLabel, and the render and execute lists of f:ajax, accept ids and search expressions, and Faces turns them into client ids; the label for="search:query" in the figure is one.

findComponent(expr) applies the same container rules to a search expression. An expression without a leading separator is relative to the closest naming container of the component it is called on (the component itself if it is one); an expression starting with the separator is absolute from the view root. The bean below prints several lookups and the container structure of a component.

IdDemo.java — findComponent and client ids from Java
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.List;

@Named
@RequestScoped
public class IdDemo {

    @Inject
    private FacesContext facesContext;

    private final List<String> lines = new ArrayList<>();

    public void lookup(ComponentSystemEvent event) {
        UIComponent root = facesContext.getViewRoot();
        UIComponent form = root.findComponent("search");
        lines.add("separator = " + UINamingContainer.getSeparatorChar(facesContext));
        lines.add("root.findComponent(\"search:query\") -> " + describe(root.findComponent("search:query")));
        lines.add("root.findComponent(\":search:query\") -> " + describe(root.findComponent(":search:query")));
        lines.add("form.findComponent(\"query\") -> " + describe(form.findComponent("query")));
        lines.add("form.findComponent(\":books:title\") -> " + describe(form.findComponent(":books:title")));
        lines.add("loose.getClientId() -> " + describe(root.findComponent("loose")));
        UIComponent query = form.findComponent("query");
        lines.add("query.getNamingContainer() -> " + query.getNamingContainer().getClientId(facesContext));
    }

    private String describe(UIComponent component) {
        return component == null ? "null" : component.getClientId(facesContext);
    }

    public List<String> getLines() {
        return lines;
    }
}
ids.xhtml — two forms, one with prependId false, and a data table
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core"
      xmlns:ui="jakarta.faces.facelets">
<f:metadata>
  <f:event type="preRenderView" listener="#{idDemo.lookup}"/>
</f:metadata>
<h:head><title>Client identifiers</title></h:head>
<h:body>
  <h:form id="search">
    <h:inputText id="query" value="#{catalog.query}"/>
  </h:form>
  <h:form id="loose" prependId="false">
    <h:inputText id="note" value="#{catalog.query}"/>
  </h:form>
  <h:dataTable id="books" value="#{catalog.books}" var="book">
    <h:column>
      <h:outputText id="title" value="#{book.title}"/>
      <h:inputText id="note" value="#{book.title}"/>
    </h:column>
  </h:dataTable>
  <ul>
    <ui:repeat value="#{idDemo.lines}" var="line">
      <li><h:outputText value="#{line}"/></li>
    </ui:repeat>
  </ul>
</h:body>
</html>

The page prints (identical on Mojarra and MyFaces):

separator = :
root.findComponent("search:query") -> search:query
root.findComponent(":search:query") -> search:query
form.findComponent("query") -> search:query
form.findComponent(":books:title") -> books:title
loose.getClientId() -> loose
query.getNamingContainer() -> search

The rendered markup of the same page shows the row indexes and the unprefixed form (Mojarra output, trimmed; the HTML comments that the render kit of a later section adds in front of each input are left out):

<form id="search" name="search" method="post" ...>
<input id="search:query" type="text" name="search:query" value="" />
...
<form id="loose" name="loose" method="post" ...>
<input id="note" type="text" name="note" value="" />
...
<table id="books">
<tr><td><span id="books:0:title">Dune</span><input id="books:0:note" type="text" name="books:0:note" value="Dune" /></td></tr>
<tr><td><span id="books:1:title">Neuromancer</span><input id="books:1:note" type="text" name="books:1:note" value="Neuromancer" /></td></tr>

The colon matters whenever a client id leaves Faces. In JavaScript, document.getElementById('search:query') works as is. In a CSS selector the colon starts a pseudo-class, so #search:query does not match and the id must be written #search\:query, or the page should style by styleClass instead; the specification lists these workarounds. In a test written with Selenium or Playwright, prefer a stable explicit id plus the form id, never a generated one.

Inspecting a Tree at Run Time

The quickest way to learn the tree is to print it. Faces publishes system events while it works, and one of them, PreRenderViewEvent, is published after the tree is built and before it is rendered. The page below subscribes to it with <f:event type="preRenderView">, which installs a ComponentSystemEventListener that calls a bean method. The bean walks the tree once recursively (to show structure, facets and client ids) and once with visitTree (to show how the visit API treats a UIData).

TreeInspector.java — a bean that walks the component tree
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.List;

@Named
@RequestScoped
public class TreeInspector {

    public record Row(String indent, String role, String type, String family, String rendererType,
                      String id, String clientId, String behaviours) {
    }

    @Inject
    private FacesContext facesContext;

    private final List<Row> rows = new ArrayList<>();
    private final List<String> titleIds = new ArrayList<>();

    /** Runs before Render Response, when the tree is complete. */
    public void collect(ComponentSystemEvent event) {
        walk(facesContext.getViewRoot(), 0, "root");
        facesContext.getViewRoot().visitTree(VisitContext.createVisitContext(facesContext), (context, component) -> {
            if ("title".equals(component.getId())) {
                titleIds.add(component.getClientId(context.getFacesContext()));
            }
            return VisitResult.ACCEPT;
        });
    }

    static String behaviours(UIComponent component) {
        if (component instanceof ClientBehaviorHolder holder && !holder.getClientBehaviors().isEmpty()) {
            return holder.getClientBehaviors().toString();
        }
        return "";
    }

    private void walk(UIComponent component, int depth, String role) {
        rows.add(new Row("  ".repeat(depth), role, component.getClass().getSimpleName(), component.getFamily(),
                component.getRendererType(), component.getId(), component.getClientId(facesContext), behaviours(component)));
        component.getFacets().forEach((name, facet) -> walk(facet, depth + 1, "facet " + name));
        for (UIComponent child : component.getChildren()) {
            walk(child, depth + 1, "child");
        }
    }

    public List<Row> getRows() {
        return rows;
    }

    public List<String> getTitleIds() {
        return titleIds;
    }
}
catalog-tree.xhtml — the catalogue page with the inspector listing appended
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core">
<f:metadata>
  <f:event type="preRenderView" listener="#{treeInspector.collect}"/>
</f:metadata>
<h:head>
  <title>Bookshelf catalogue</title>
</h:head>
<h:body>
  <h:form id="search">
    <h:outputLabel for="query" value="Title contains"/>
    <h:inputText id="query" value="#{catalog.query}">
      <f:ajax event="keyup" render="books"/>
    </h:inputText>
    <h:commandButton id="go" value="Search" action="#{catalog.search}"/>
  </h:form>
  <h:dataTable id="books" value="#{catalog.books}" var="book">
    <f:facet name="header">Catalogue</f:facet>
    <h:column>
      <f:facet name="header">Title</f:facet>
      <h:outputText id="title" value="#{book.title}"/>
    </h:column>
    <h:column>
      <f:facet name="header">Author</f:facet>
      <h:outputText value="#{book.author}"/>
    </h:column>
  </h:dataTable>

  <h:dataTable id="tree" value="#{treeInspector.rows}" var="row">
    <h:column><h:outputText value="#{row.indent}#{row.type}"/></h:column>
    <h:column><h:outputText value="#{row.role}"/></h:column>
    <h:column><h:outputText value="#{row.family}"/></h:column>
    <h:column><h:outputText value="#{row.rendererType}"/></h:column>
    <h:column><h:outputText value="#{row.id}"/></h:column>
    <h:column><h:outputText value="#{row.clientId}"/></h:column>
    <h:column><h:outputText value="#{row.behaviours}"/></h:column>
  </h:dataTable>
  <p>Found by visitTree: <h:outputText value="#{treeInspector.titleIds}"/></p>
</h:body>
</html>

The supporting pieces are the plain domain classes and the catalogue bean the page uses.

Book.java, CatalogService.java and CatalogBean.java
package org.example.bookshelf;

public record Book(long id, String title, String author, int year) {
}
package org.example.bookshelf;

import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;

@ApplicationScoped
public class CatalogService {

    private final List<Book> books = List.of(
            new Book(1, "Dune", "Frank Herbert", 1965),
            new Book(2, "Neuromancer", "William Gibson", 1984),
            new Book(3, "Snow Crash", "Neal Stephenson", 1992));

    public List<Book> findAll() {
        return books;
    }

    public List<Book> search(String text) {
        String needle = text == null ? "" : text.toLowerCase();
        return books.stream().filter(b -> b.title().toLowerCase().contains(needle)).toList();
    }
}
package org.example.bookshelf;

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;

@Named("catalog")
@RequestScoped
public class CatalogBean {

    @Inject
    private CatalogService service;

    private String query = "";
    private List<Book> books;

    @PostConstruct
    void init() {
        books = service.findAll();
    }

    public String search() {
        books = service.search(query);
        return null;
    }

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

    public List<Book> getBooks() {
        return books;
    }
}

Rendering the page, and dropping the literal-text nodes (UIInstructions) and the script resource of the Ajax library, the inspector reports the following tree on Mojarra 4.1.17:

Table 5. The Bookshelf tree as printed by the inspector (Mojarra 4.1.17)
Component Role Family Renderer type Id Client id

UIViewRoot

root

jakarta.faces.ViewRoot

none

j_id1

j_id1

HtmlBody

child

jakarta.faces.Output

jakarta.faces.Body

j_idt4

j_idt4

HtmlForm

child

jakarta.faces.Form

jakarta.faces.Form

search

search

HtmlOutputLabel

child

jakarta.faces.Output

jakarta.faces.Label

j_idt5

search:j_idt5

HtmlInputText

child

jakarta.faces.Input

jakarta.faces.Text

query

search:query

HtmlCommandButton

child

jakarta.faces.Command

jakarta.faces.Button

go

search:go

HtmlDataTable

child

jakarta.faces.Data

jakarta.faces.Table

books

books

HtmlColumn

child

jakarta.faces.Column

none

j_idt7

books:j_idt7

HtmlOutputText

child

jakarta.faces.Output

jakarta.faces.Text

title

books:title

The facets of the table and the columns appear in the listing as rows with the role facet header, and the head resource container (which holds the Ajax script because the page uses f:ajax) as a facet of the view root named jakarta_faces_location_HEAD on Mojarra and jakarta_faces_location_head on MyFaces. The page also prints the result of the visitTree pass:

Found by visitTree: [books:0:title, books:1:title, books:2:title]

The recursive walk saw one title component, with the client id books:title; the visit API saw three, one per row, with the row index in the id. That is the correct behaviour: UIData repeats its children by moving a row cursor inside itself, and visitTree (like invokeOnComponent) positions the cursor for each row, while a raw getChildren() walk does not. Code that processes components inside tables must use the visit API.

The inspector’s last column prints the client behaviours of a holder. The input of the page carries <f:ajax event="keyup" render="books"/>, so its row prints (the identity hash differs on each run):

{keyup=[jakarta.faces.component.behavior.AjaxBehavior@c8ac1e2]}

Value Holders

A component that shows or edits a value implements ValueHolder; one that accepts input implements EditableValueHolder. UIOutput and its subclasses are value holders, UIInput and its subclasses are editable. The interfaces describe three views of a value, and understanding which one you are looking at removes most confusion about conversion and validation:

  • the model value, behind the value expression, for example a String property of a bean

  • the local value (getLocalValue()): the value held by the component itself, after conversion and validation succeeded and before it is pushed to the model

  • the submitted value (getSubmittedValue()): the raw, unconverted value the browser sent, kept in the component until validation

getValue() returns the local value if it is set and otherwise evaluates the value expression, which is how a page displays model data on the first request. The properties converter (ValueHolder) and required, immediate, valid, localValueSet, submittedValue and the validators (EditableValueHolder) describe conversion and validation; the converters and validators pages of this section cover them in depth. The state transitions are visible from a postback. The bean below records the state of one input at two moments: right after validation, through the postValidate system event, and in the action method that runs later.

ValueStates.java — the three values at two points of one request
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.UIComponent;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.List;

@Named
@RequestScoped
public class ValueStates {

    @Inject
    private FacesContext facesContext;

    private final List<String> lines = new ArrayList<>();

    public void afterValidate(ComponentSystemEvent event) {
        record("after validation", event.getComponent());
    }

    public String action() {
        record("in the action", facesContext.getViewRoot().findComponent("states:title"));
        return null;
    }

    private void record(String when, UIComponent component) {
        EditableValueHolder input = (EditableValueHolder) component;
        lines.add(when + ": submitted=" + input.getSubmittedValue() + ", local=" + input.getLocalValue()
                + ", localValueSet=" + input.isLocalValueSet() + ", valid=" + input.isValid());
    }

    public List<String> getLines() {
        return lines;
    }
}
states.xhtml — the page that uses it
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core"
      xmlns:ui="jakarta.faces.facelets">
<h:head><title>Value states</title></h:head>
<h:body>
  <h:form id="states">
    <h:inputText id="title" value="#{catalog.query}">
      <f:event type="postValidate" listener="#{valueStates.afterValidate}"/>
    </h:inputText>
    <h:commandButton id="go" value="Go" action="#{valueStates.action}"/>
  </h:form>
  <ul>
    <ui:repeat value="#{valueStates.lines}" var="line">
      <li><h:outputText value="#{line}"/></li>
    </ui:repeat>
  </ul>
</h:body>
</html>

Submitting the value Dune prints, on both implementations:

after validation: submitted=null, local=Dune, localValueSet=true, valid=true
in the action: submitted=null, local=null, localValueSet=false, valid=true

After Process Validations the raw submitted value has been converted and moved into the local value. After Update Model Values the value has been written to #{catalog.query} and the component cleared its local value, so that the next render reads the model again. A validation failure leaves the submitted value in place and the component invalid, which is what makes the input redisplay what the user typed.

ActionSource is the equivalent contract for components that fire actions: it holds the action listeners, the immediate flag and, since Faces 4.1, the action method expression. In Faces 4.0 and earlier the expression lived in ActionSource2, and UICommand implemented that interface; Faces 4.1 moved the methods into ActionSource as default methods and kept ActionSource2 for compatibility.

Behaviours

A behaviour is something a component attaches to a client-side event: the standard one is Ajax. A component that can carry behaviours implements jakarta.faces.component.behavior.ClientBehaviorHolder, with addClientBehavior(eventName, behavior), getClientBehaviors() (a map from event name to the list of behaviours), getEventNames() and getDefaultEventName(). The standard HTML components implement it, and UIComponentBase provides the default method bodies. The behaviours themselves implement ClientBehavior; AjaxBehavior is the one that f:ajax creates. The default event of a component is what f:ajax uses when you omit event: valueChange for an input and action for a command, for the standard components.

A behaviour is rendered by a ClientBehaviorRenderer, looked up in the render kit by the behaviour’s renderer type, which produces the script placed in the markup. In the inspector page the f:ajax event="keyup" produced, in the rendered input, the handler onkeyup="mojarra.ab(this,event,'keyup',0,'books')" on Mojarra and onkeyup="myfaces.ab(this,event,'keyup','','books',{})" on MyFaces: the same behaviour, written by the implementation’s own client behaviour renderer. During Apply Request Values the renderer’s decode enqueues the server-side BehaviorEvent for the request. The Ajax page of this section covers f:ajax and the JavaScript API; custom behaviours are registered with @FacesBehavior and rendered by a class annotated with @FacesBehaviorRenderer.

Renderers and Render Kits

The specification offers two programming models for turning components into markup and request parameters back into values. In the direct implementation model the component decodes and encodes itself by overriding decode, encodeBegin, encodeChildren and encodeEnd. In the delegated implementation model the component has a rendererType and delegates those operations to a Renderer found through the render kit. The standard components use the delegated model, and this is the split that gives Faces its flexibility: the component class holds the behaviour and the state (a UICommand fires actions), and a renderer decides what it looks like (button or link). One component can have several renderers, and one renderer can serve several components of a family.

A Renderer<T extends UIComponent> (generic since Faces 4.0, so the renderer of the example below can extend Renderer<RatingInput>) has these operations, each tied to a phase:

  • decode(context, component): Apply Request Values; read the request parameter named by the client id and call setSubmittedValue

  • getConvertedValue(context, component, submittedValue): Process Validations; turn the submitted value into the component’s value, or throw ConverterException

  • encodeBegin, encodeChildren, encodeEnd: Render Response; write the markup, with getRendersChildren() telling Faces whether the renderer or the framework walks the children

  • convertClientId(context, clientId): last chance to rewrite the client id for the client

A RenderKit is a set of renderers for one kind of client, found by family and renderer type: getRenderer(family, rendererType). It also creates the ResponseWriter (for text markup) and the ResponseStream (the OutputStream adapter for binary output, for example a component that streams an image), holds the ClientBehaviorRenderer instances and the ResponseStateManager. Every implementation must provide the default kit, named by the constant RenderKitFactory.HTML_BASIC_RENDER_KIT, whose id is HTML_BASIC and which produces HTML; its required behaviour is the normative "Standard HTML RenderKit" documentation published with the specification. Page authors only select a kit indirectly: the renderKitId of the view root, set by <f:view renderKitId="...">, <default-render-kit-id> in faces-config.xml, or a custom ViewHandler.calculateRenderKitId.

A Custom Component and Its Renderers

A custom component is one class that extends UIComponentBase or a standard component, plus, for the delegated model, a renderer. The example is a rating input for the Bookshelf reviews. RatingInput extends UIInput, so it inherits decoding, conversion, validation, model update and value-change events. It gets its own family so that renderers for it are not confused with those of the standard inputs, it sets a default renderer type in its constructor, and it stores its max property through the StateHelper, so the value survives between requests.

RatingInput.java — the component, with state kept in the StateHelper
package org.example.bookshelf;

import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIInput;

@FacesComponent(value = "bookshelf.RatingInput", createTag = true,
        namespace = "urn:bookshelf:components", tagName = "rating")
public class RatingInput extends UIInput {

    public static final String COMPONENT_FAMILY = "bookshelf.Rating";
    public static final String RENDERER_TYPE = "bookshelf.Stars";

    private enum PropertyKeys {
        max
    }

    public RatingInput() {
        setRendererType(RENDERER_TYPE);
    }

    @Override
    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public int getMax() {
        return (Integer) getStateHelper().eval(PropertyKeys.max, 5);
    }

    public void setMax(int max) {
        getStateHelper().put(PropertyKeys.max, max);
    }
}

The annotation @FacesComponent registers the component type; createTag = true with namespace and tagName makes Faces generate a Facelets tag for it, so no tag library file is needed. getStateHelper().put(key, value) and eval(key, default) store and read properties in the structure that Faces saves and restores with the view; this replaces the hand-written saveState and restoreState methods that returned an Object[] in the older component model, and it is the reason max needs no state code of its own. UIComponentBase still has saveState and restoreState for components that hold state outside the helper.

The renderer decodes the radio group, converts the string to an Integer, and writes the markup with the ResponseWriter. The @FacesRenderer annotation registers it in the default render kit under the family and renderer type the component declares.

StarsRenderer.java — decode, getConvertedValue and encodeEnd
package org.example.bookshelf;

import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.convert.ConverterException;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.render.FacesRenderer;
import jakarta.faces.render.Renderer;
import java.io.IOException;
import java.util.Map;

@FacesRenderer(componentFamily = RatingInput.COMPONENT_FAMILY, rendererType = RatingInput.RENDERER_TYPE)
public class StarsRenderer extends Renderer<RatingInput> {

    @Override
    public void decode(FacesContext context, RatingInput component) {
        Map<String, String> params = context.getExternalContext().getRequestParameterMap();
        String clientId = component.getClientId(context);
        if (params.containsKey(clientId)) {
            component.setSubmittedValue(params.get(clientId));
        }
    }

    @Override
    public void encodeEnd(FacesContext context, RatingInput component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        String clientId = component.getClientId(context);
        char separator = UINamingContainer.getSeparatorChar(context);
        Object current = component.getSubmittedValue() != null ? component.getSubmittedValue() : component.getValue();
        writer.startElement("span", component);
        writer.writeAttribute("id", clientId, "id");
        writer.writeAttribute("class", "rating", null);
        for (int star = 1; star <= component.getMax(); star++) {
            writer.startElement("input", component);
            writer.writeAttribute("type", "radio", null);
            writer.writeAttribute("id", clientId + separator + star, null);
            writer.writeAttribute("name", clientId, null);
            writer.writeAttribute("value", star, null);
            if (current != null && current.toString().equals(String.valueOf(star))) {
                writer.writeAttribute("checked", "checked", null);
            }
            writer.endElement("input");
        }
        writer.endElement("span");
    }

    @Override
    public Object getConvertedValue(FacesContext context, RatingInput component, Object submitted) {
        if (submitted == null || submitted.toString().isEmpty()) {
            return null;
        }
        try {
            return Integer.valueOf(submitted.toString());
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Not a rating", null), e);
        }
    }
}

A second renderer for the same family shows the split at work. It writes a select element instead of radio buttons and reuses the decoding and conversion, so only encoding changes. Because the renderer type is a property of the component, choosing it means choosing a component class that sets it; the small subclass below does that and gets its own tag.

MenuRenderer.java and RatingMenu.java — same component behaviour, different markup
package org.example.bookshelf;

import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.render.FacesRenderer;
import java.io.IOException;

@FacesRenderer(componentFamily = RatingInput.COMPONENT_FAMILY, rendererType = RatingMenu.MENU_RENDERER_TYPE)
public class MenuRenderer extends StarsRenderer {

    @Override
    public void encodeEnd(FacesContext context, RatingInput component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        String clientId = component.getClientId(context);
        Object current = component.getSubmittedValue() != null ? component.getSubmittedValue() : component.getValue();
        writer.startElement("select", component);
        writer.writeAttribute("id", clientId, "id");
        writer.writeAttribute("name", clientId, null);
        for (int star = 1; star <= component.getMax(); star++) {
            writer.startElement("option", component);
            writer.writeAttribute("value", star, null);
            if (current != null && current.toString().equals(String.valueOf(star))) {
                writer.writeAttribute("selected", "selected", null);
            }
            writer.writeText(star + " / " + component.getMax(), null);
            writer.endElement("option");
        }
        writer.endElement("select");
    }
}
package org.example.bookshelf;

import jakarta.faces.component.FacesComponent;

@FacesComponent(value = "bookshelf.RatingMenu", createTag = true,
        namespace = "urn:bookshelf:components", tagName = "ratingMenu")
public class RatingMenu extends RatingInput {

    public static final String MENU_RENDERER_TYPE = "bookshelf.Menu";

    public RatingMenu() {
        setRendererType(MENU_RENDERER_TYPE);
    }
}
rating.xhtml and the supporting bean
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:b="urn:bookshelf:components">
<h:head><title>Rating</title></h:head>
<h:body>
  <h:form id="rate">
    <b:rating id="stars" max="5" value="#{reviewDraft.stars}"/>
    <b:ratingMenu id="menu" max="5" value="#{reviewDraft.stars}"/>
    <h:commandButton id="save" value="Save" action="#{reviewDraft.save}"/>
    <h:messages/>
  </h:form>
</h:body>
</html>
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;

@Named
@RequestScoped
public class ReviewDraft {

    private Integer stars = 4;

    public String save() {
        return null;
    }

    public Integer getStars() {
        return stars;
    }

    public void setStars(Integer stars) {
        this.stars = stars;
    }
}

Rendering the page produces one radio group and one select, both for the same component behaviour (Mojarra output, line breaks added):

<span id="rate:stars" class="rating">
  <input type="radio" id="rate:stars:1" name="rate:stars" value="1" />
  ...
  <input type="radio" id="rate:stars:4" name="rate:stars" value="4" checked="checked" />
  <input type="radio" id="rate:stars:5" name="rate:stars" value="5" />
</span>
<select id="rate:menu" name="rate:menu">
  <option value="1">1 / 5</option> ... <option value="4" selected="selected">4 / 5</option> ...
</select>

Posting abc as the value of rate:stars is rejected by the renderer’s getConvertedValue: the page redisplays the form with the message Not a rating, on both implementations, and the model is untouched. The renderer follows three rules that apply to every renderer. It writes through the ResponseWriter (startElement, writeAttribute, endElement, writeText) and never concatenates raw strings, so attribute values and text are escaped. It uses the component’s client id for the element id and the parameter name, and builds child ids with UINamingContainer.getSeparatorChar(context). And it reads getSubmittedValue() before getValue() when it redisplays, so a rejected input shows what the user typed.

Custom RenderKits

A render kit is one more object you can replace, and the specification describes three ways, in increasing strength.

  • Add or replace renderers in an existing kit. A @FacesRenderer annotation, or a <renderer> entry in faces-config.xml, registers a renderer in a kit (the default one unless renderKitId is given). This is how the rating renderers above work, and how a library supplies its components.

  • Decorate a kit. When a <render-kit> element names an id that already exists, and its <render-kit-class> has a constructor taking a RenderKit, the existing kit is passed to the constructor and the result replaces it in the RenderKitFactory. RenderKitWrapper and RendererWrapper are the base classes for this.

  • Register a new kit under a new id. RenderKitFactory.addRenderKit(id, kit), or a <render-kit> with a fresh id, makes a complete alternative available, which a view selects through its renderKitId.

The example decorates the default kit. It wraps the renderer of h:inputText so that every input is preceded by an HTML comment naming its client id, which is a useful trick for tracing a rendered page back to the tree. Only the renderers for the family jakarta.faces.Input and renderer type jakarta.faces.Text are wrapped; all other lookups pass through, which matters because the implementations' own renderers are looked up by many internal paths.

CommentingRenderKit.java — a RenderKitWrapper and a RendererWrapper
package org.example.bookshelf;

import jakarta.faces.component.UIComponent;
import jakarta.faces.context.FacesContext;
import jakarta.faces.render.RenderKit;
import jakarta.faces.render.RenderKitWrapper;
import jakarta.faces.render.Renderer;
import jakarta.faces.render.RendererWrapper;
import java.io.IOException;

public class CommentingRenderKit extends RenderKitWrapper {

    public CommentingRenderKit(RenderKit wrapped) {
        super(wrapped);
    }

    @Override
    public Renderer getRenderer(String family, String rendererType) {
        Renderer renderer = super.getRenderer(family, rendererType);
        if (renderer != null && "jakarta.faces.Input".equals(family) && "jakarta.faces.Text".equals(rendererType)) {
            return new CommentingRenderer(renderer);
        }
        return renderer;
    }

    private static final class CommentingRenderer extends RendererWrapper {

        CommentingRenderer(Renderer wrapped) {
            super(wrapped);
        }

        @Override
        public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
            context.getResponseWriter().writeComment(" input " + component.getClientId(context) + " ");
            super.encodeEnd(context, component);
        }
    }
}
faces-config.xml — registering the wrapper and, for the next section, a factory
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_1.xsd"
              version="4.1">
  <factory>
    <faces-context-factory>org.example.bookshelf.TimingFacesContextFactory</faces-context-factory>
  </factory>
  <render-kit>
    <render-kit-id>HTML_BASIC</render-kit-id>
    <render-kit-class>org.example.bookshelf.CommentingRenderKit</render-kit-class>
  </render-kit>
</faces-config>

With the wrapper in place, the first catalogue page renders each input with its comment. The two implementations place the comment slightly differently, because each default text renderer decides for itself when it opens its element (Mojarra before the input, MyFaces after it), so do not rely on the position of text written around a renderer’s output:

<label for="search:query">Title contains</label><!-- input search:query --><input id="search:query" ... />

Writing the comment with ResponseWriter.writeComment rather than write keeps it well formed in both. The wrapper also shows why decoration beats copying: it adds one behaviour and inherits the rest of the kit, including state handling and future changes of the implementation.

The era of "rich" render kits is over. Books of the Faces 1.x period describe render kits that targeted browsers with XUL or HTC (HTML Components) behaviours and showed how to switch kits at run time; those client technologies are dead, and no current browser runs them. What remains valuable is the mechanism: the component stays the same and the renderer is looked up through family and rendererType. Today’s reasons to register another kit or renderer are different markup for a CSS framework, accessibility fixes, or output that is not HTML, and the HTML_BASIC kit is overwhelmingly the only one in use.

Factories and the Decorator Pattern

Faces creates its central objects through factories, and the factories are replaceable. FactoryFinder.getFactory(name) returns the factory registered under one of the constants of jakarta.faces.FactoryFinder:

  • request and view: FACES_CONTEXT_FACTORY, EXTERNAL_CONTEXT_FACTORY, LIFECYCLE_FACTORY, PARTIAL_VIEW_CONTEXT_FACTORY, VIEW_DECLARATION_LANGUAGE_FACTORY, FLASH_FACTORY, CLIENT_WINDOW_FACTORY, FLOW_HANDLER_FACTORY

  • application and rendering: APPLICATION_FACTORY, RENDER_KIT_FACTORY, EXCEPTION_HANDLER_FACTORY

  • tree and Facelets: VISIT_CONTEXT_FACTORY, FACELET_CACHE_FACTORY, TAG_HANDLER_DELEGATE_FACTORY, SEARCH_EXPRESSION_CONTEXT_FACTORY

Each is registered in faces-config.xml inside <factory> (for example <faces-context-factory>), and each element adds a layer on top of the previous implementation. The mechanism is the decorator pattern, and the specification makes it uniform for every class that implements FacesWrapper: if the configured class has a constructor taking a single argument of the type it replaces, the runtime passes in the implementation that previously filled the role, and getWrapped() returns it. The custom class overrides only what it wants to change and delegates the rest. The specification lists the covered types, among them Application (ApplicationWrapper), ExternalContext (ExternalContextWrapper), FacesContext (FacesContextWrapper), NavigationHandler, ViewHandler, ResourceHandler, ExceptionHandler, Lifecycle, RenderKit, Renderer, ResponseWriter, StateManager, ViewDeclarationLanguage, VisitContext and Flash.

The example decorates the FacesContext factory, so that every request logs how long the Faces lifecycle took. The factory wraps the context it creates in a FacesContextWrapper that overrides release(), the call that ends the request.

TimingFacesContextFactory.java — a FacesContextFactory and a FacesContextWrapper
package org.example.bookshelf;

import jakarta.faces.FacesException;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.FacesContextFactory;
import jakarta.faces.context.FacesContextWrapper;
import jakarta.faces.lifecycle.Lifecycle;
import java.util.logging.Logger;

public class TimingFacesContextFactory extends FacesContextFactory {

    private static final Logger LOG = Logger.getLogger(TimingFacesContextFactory.class.getName());

    public TimingFacesContextFactory(FacesContextFactory wrapped) {
        super(wrapped);
    }

    @Override
    public FacesContext getFacesContext(Object context, Object request, Object response, Lifecycle lifecycle)
            throws FacesException {
        FacesContext delegate = getWrapped().getFacesContext(context, request, response, lifecycle);
        return new TimingFacesContext(delegate);
    }

    private static final class TimingFacesContext extends FacesContextWrapper {

        private final long started = System.nanoTime();

        TimingFacesContext(FacesContext wrapped) {
            super(wrapped);
        }

        @Override
        public void release() {
            LOG.info(() -> "Faces request took " + (System.nanoTime() - started) / 1_000_000 + " ms");
            super.release();
        }
    }
}

It is registered in the faces-config.xml shown above. Every request to the scratch project then logs one line, for example Faces request took 72 ms for the first request and a few milliseconds afterwards, on both implementations.

The decorator is one of the patterns the architecture is built from, and naming them makes the design easier to read.

Table 6. Design patterns in the Faces architecture
Pattern Where it appears

Composite

The component tree: a UIComponent contains UIComponent children and facets, and the lifecycle calls the same method on the root and on every node

Strategy

Renderers and render kits: the component delegates how it looks to an interchangeable Renderer

Observer

Application events, component system events and phase listeners; Application.subscribeToEvent and f:event

Decorator

Wrappers and factories: every replaceable object receives the previous implementation and overrides a subset

Front controller

FacesServlet, the single entry point that runs the lifecycle for every request

MVC

Model: CDI beans; view: the component tree and Facelets; controller: FacesServlet, the lifecycle and the action methods

The binding Attribute and Its Pitfalls

Every component tag accepts a binding attribute: "the ValueExpression linking this component to a property in a backing bean", which must evaluate to a UIComponent. The specification calls it a component binding, established by calling setValueExpression("binding", ...), and it must point to a read-write JavaBeans property of type UIComponent or a subclass. It is used at two moments:

  • When a component instance is first created, typically by a tag in the page, the implementation calls the getter of the expression. If it returns a non-null component, that instance is added to the tree: this is how a bean supplies a programmatically created component. If it returns null, a new component is created, added to the tree, and the setter is called with it.

  • When the tree is recreated in Restore View, the setter is called with the recreated component instance, for every component with a binding.

The first example is the classic use: give the bean access to a component so that it can act on it. A review form attaches a message to the comment input and clears it after saving.

ReviewForm.java — a request-scoped bean bound to one input
package org.example.bookshelf;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.component.UIInput;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@RequestScoped
public class ReviewForm {

    @Inject
    private FacesContext facesContext;

    private UIInput commentInput;
    private String comment;

    public String save() {
        // the review would be stored here
        facesContext.addMessage(commentInput.getClientId(facesContext),
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Review saved", null));
        comment = null;
        commentInput.resetValue();
        return null;
    }

    public UIInput getCommentInput() {
        return commentInput;
    }

    public void setCommentInput(UIInput commentInput) {
        this.commentInput = commentInput;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }
}
review.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html">
<h:head><title>Review</title></h:head>
<h:body>
  <h:form id="review">
    <h:inputTextarea id="comment" binding="#{reviewForm.commentInput}" value="#{reviewForm.comment}"/>
    <h:message id="commentMessage" for="comment"/>
    <h:commandButton id="save" value="Save" action="#{reviewForm.save}"/>
  </h:form>
</h:body>
</html>

After saving, the h:message shows Review saved next to the comment field and the textarea is empty. Clearing both the model value (comment = null) and the component (resetValue()) is intentional: the model value is read again on the next render, and resetValue() clears the submitted value and validity flags of the component so that a previously failed validation does not leave stale state behind.

The second example uses the other direction: the bean creates the component and the tag adopts it. The creation happens in @PostConstruct, so it runs once, when the bean is first used, which is when the tag evaluates the binding. The getter stays free of logic.

BadgeBean.java — a bean that supplies the component
package org.example.bookshelf;

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.application.Application;
import jakarta.faces.component.html.HtmlOutputText;
import jakarta.faces.component.html.HtmlPanelGroup;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;

@Named
@RequestScoped
public class BadgeBean {

    @Inject
    private FacesContext facesContext;

    private HtmlPanelGroup badges;

    /** Runs the first time the tag evaluates the binding, while the view is built. */
    @PostConstruct
    void createBadges() {
        Application application = facesContext.getApplication();
        badges = (HtmlPanelGroup) application.createComponent(HtmlPanelGroup.COMPONENT_TYPE);
        badges.setLayout("block");
        for (String label : List.of("Classic", "Award winner")) {
            HtmlOutputText badge = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
            badge.setValue(label);
            badge.setStyleClass("badge");
            badges.getChildren().add(badge);
        }
    }

    public HtmlPanelGroup getBadges() {
        return badges;
    }

    public void setBadges(HtmlPanelGroup badges) {
        this.badges = badges;
    }

    public String reload() {
        return null;
    }
}
badges.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html">
<h:head><title>Badges</title></h:head>
<h:body>
  <h:form id="badgesForm">
    <h:panelGroup id="badges" binding="#{badgeBean.badges}"/>
    <h:commandButton id="again" value="Reload" action="#{badgeBean.reload}"/>
  </h:form>
</h:body>
</html>

The page renders one div with two span elements, and pressing the button and redisplaying the page renders the same two spans, not four: the request-scoped bean is created again, and again supplies the component, while Faces restores the tree from the page. Components that are added in an action method, or in a listener that runs on every request, without checking what is already in the tree are a classic source of duplicated children and clashing ids, which is why the creation above happens before the tree reads the property.

Table 7. Pitfalls of binding
Pitfall Why it happens and what to do

Scope other than request

The specification says the system cannot behave correctly if a bean referenced by a binding is in any scope wider than request: UIComponent instances depend on running inside a single thread, so a wider scope would require thread safety, and holding them in session or view scope also has negative memory effects. A component held by a session or application bean is one instance shared by several requests and users. Keep such beans request scoped, and put the application state in a separate bean with the right scope

One property, one component

A property holds one instance. If the tag is processed several times (inside a repeated or included fragment, or in several composite components), each tree build overwrites the property and the bean sees only the last. Use a different mechanism for repeated components

The property is null when the tag was not processed

The setter is called only when the component is created. If the tag is skipped at build time, the property stays null and code that reads it fails

Using it before the tree exists

The bean property is set while the view is built. Code that runs earlier, for example in a constructor or in an earlier phase listener, sees null. After Restore View, and in action methods and Render Response, it is set

The wrong property type

A property typed HtmlInputText cannot receive a component of another class, so changing the tag breaks the bean. Declare the most general type that works (UIInput, UIComponent)

Building components in getters

The getter is called many times per request. Create components once, in @PostConstruct, and keep logic out of getters

Reaching for binding when something simpler works

Most needs are met without it, see below

The alternatives are usually better. Inside an action listener, ActionEvent.getComponent() gives the source component; a ComponentSystemEvent listener (the preRenderView listener above) receives it through getComponent(); UIComponent.getCurrentComponent(facesContext) returns the one being processed; findComponent locates another by id; and f:attribute or f:param pass data to a listener. A message for a specific field only needs its client id, and a validator only needs the UIComponent it is handed. Reserve binding for the case that really requires an instance: programmatic creation of a subtree, or direct access to a component’s state from a request-scoped bean.

Where Each Part Acts in the Lifecycle

The objects above show up in every request. The table maps them to the six standard phases (the request processing lifecycle page of this section covers the phases themselves).

Table 8. The architecture, phase by phase
Phase What happens to the objects on this page

Restore View

FacesServlet creates the FacesContext through the factory; the ViewHandler finds the view definition and the UIViewRoot is created or restored; on a postback the tree is rebuilt and saved state applied, binding setters run, and the PostAddToView and PostRestoreState system events are published

Apply Request Values

processDecodes walks the tree; each component’s decode() runs, which for the delegated model calls Renderer.decode, which reads the request parameter for the client id into the submitted value; behaviours' renderers enqueue events

Process Validations

processValidators walks the tree; UIInput.validate() converts through Renderer.getConvertedValue or the converter, runs validators and publishes the pre- and post-validate events

Update Model Values

processUpdates walks the tree; UIInput.updateModel() writes the local value to the model through the value expression and clears the local value

Invoke Application

Queued events are broadcast: the UICommand action runs, navigation is decided

Render Response

ViewHandler.renderView builds the tree for an initial request, publishes PreRenderViewEvent, then calls encodeAll: Renderer.encodeBegin, children, encodeEnd write through the ResponseWriter; the state is saved and PostRenderViewEvent published; FacesContext.release() runs when the servlet finishes

Mojarra Versus MyFaces

The specification fixes the contracts on this page, and the two implementations follow them, so the examples ran unchanged on both. What differs is everything the specification leaves open, and those differences show up in the tree and the markup. They were observed running the same project on Mojarra 4.1.17 and MyFaces 4.1.4.

Table 9. Differences observed in the scratch project
Aspect Mojarra 4.1.17 MyFaces 4.1.4

Generated component ids

j_idt plus a number (j_idt5); view root j_id1

j_id_ plus a base-36 counter (j_id_7, j_id_h); view root j_id__v_0

Head resource facet of the view root

jakarta_faces_location_HEAD

jakarta_faces_location_head

Form marker field

a hidden field named after the form id (<input type="hidden" name="search" value="search" />)

a hidden field named search_SUBMIT with value 1; a postback test client must send it

Ajax script name

mojarra.ab(...)

myfaces.ab(...)

View-state hidden field id

j_id1:jakarta.faces.ViewState:0

j_id__v_0:jakarta.faces.ViewState:1

Table body

tbody without id

tbody with id="books:tbody_element"

Position of text a wrapper writes around a text renderer’s output

before the input

after the input

The practical rules follow directly: give every component whose id you refer to from scripts, CSS, tests or f:ajax an explicit id; never copy a generated id; do not depend on the exact markup of a standard renderer, only on its documented contract; and when you wrap or write renderers, test them on the implementation you deploy. Implementation specific context parameters (org.apache.myfaces.* for MyFaces, the Mojarra CONTEXT-PARAMS list) are covered on the versions and implementations page of this section.

Several frameworks in this documentation keep a server-side tree of UI objects, and the comparison clarifies what is specific to Faces.

  • ASP.NET Web Forms builds a control tree from runat="server" markup, processes it through a page lifecycle with postbacks, and carries state in ViewState, which is the same shape as the Faces tree with its saved state; see Server Controls, ViewState and Control State and Page Lifecycle & Postback. A Web Forms control typically writes its own markup, whereas Faces separates the component from its renderer and lets you swap renderers or whole render kits.

  • Vaadin also keeps a server-side component tree, but the browser draws it with client-side components that the server keeps in sync through a protocol; Faces sends HTML written by renderers on the server and updates parts of the page with Ajax. See the Vaadin Reference.

  • Thymeleaf with Spring MVC has no component tree at all: a template is rendered from a model and the result is markup. See Thymeleaf and the overview of server-side options in Server-Side Web UI Frameworks.

What Changed per Version

Table 10. Changes that affect the objects on this page
Version Change

2.0

Facelets becomes the default view declaration language, annotations such as @FacesComponent and @FacesRenderer replace most faces-config.xml registration, component system events and the visit API (visitTree) are introduced, behaviours and Ajax enter the component model, StateHelper and partial state saving are added, and ActionSource is superseded by ActionSource2

2.2

Stateless views (<f:view transient="true">)

2.3

@Inject support for Faces objects such as FacesContext and ExternalContext, the search expression framework, and explicit support for dynamic component manipulation

3.0

Namespace change from javax.faces to jakarta.faces

4.0

JSP support, native managed beans, MethodBinding, ValueBinding, the CURRENT_COMPONENT constants of UIComponent, deprecated StateManager methods and ResourceResolver removed; the namespaces are URNs (jakarta.faces.html, jakarta.faces.core, …​); new UIViewRoot#getDoctype(), FacesContext#getLifecycle(), generic Renderer<T extends UIComponent> and automatic extensionless mapping; UIComponent#subscribeToEvent made more convenient; Java SE 11 or later

4.1

ActionSource and ActionSource2 unified: UICommand implements ActionSource and the expression methods are default methods; full state saving deprecated, along with the jakarta.faces.PARTIAL_STATE_SAVING and jakarta.faces.FULL_STATE_SAVING_VIEW_IDS parameters; Java SE 17 or later

5.0 (upcoming)

Milestone 5.0.0-M9 of 2026-09-18, aligned with Jakarta EE 12 (Java SE 21 in the EE 12 plan); the Faces issue tracker has a closed 5.0 item to remove support for the old java.sun.com and xmlns.jcp.org namespaces. Plan against 4.1 and watch the project for the final list

Where the Books Are Outdated

The Faces books of 2004 to 2010 explain this architecture well; the concepts (component and renderer split, component tree, families, facets, render kits) are the same today. The details they teach are not.

  • JSP and tag handlers. The older books register a component for a page with a JSP tag handler (UIComponentELTag) and a tag library descriptor, and they set the renderer type in the handler. JSP views were deprecated by Facelets in 2.0 and removed in 4.0, so none of that exists. The current registration is @FacesComponent (with createTag) or a Facelets tag library, as in the rating example above.

  • Registration in faces-config.xml. Components, renderers and converters were declared in XML. Annotations (@FacesComponent, @FacesRenderer, @FacesConverter, @FacesBehavior) now do the job.

  • Hand-written state saving. Custom components in the older books implement saveState and restoreState returning an Object[]. Use getStateHelper(); implement the methods only for state outside it.

  • ValueBinding, MethodBinding, getValueBinding. Removed in 4.0; value expressions replaced them in 1.2 and 2.0.

  • javax.faces.* and the old namespaces. Everything is jakarta.faces.* since 3.0, and the URNs since 4.0.

  • XUL and HTC render kits. The chapters on switching render kits at run time to target XUL or HTC clients describe dead browser technologies. The render kit mechanism is current, the examples are not.

  • Managed beans. @ManagedBean and managed-bean entries in faces-config.xml (removed in 4.0) are replaced by CDI @Named beans, which is also what lets you @Inject FacesContext.

  • Extending core classes by subclassing the implementation. Some older advice subclasses implementation classes. Use the wrappers and the factory decoration of Factories and the Decorator Pattern, which the specification guarantees.

  • Weblets and Dynamic Faces for component resources. Resources are served by the ResourceHandler, covered on the resource handling page of this section.