Core Tags (f:)

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-api 4.1.2, part of Jakarta EE 11), Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4, with Java 17 or later. Every snippet below was compiled and deployed as a scratch Bookshelf WAR on Tomcat 11.0.26 with Weld 6.0.4.Final, once with Mojarra and once with MyFaces, and the described output is what came back from curl requests and form posts (checked on 2026-10-08). Where the two implementations differ, the text says so. Faces 4.0 behaviour was not run separately; the one tag below that is newer than 4.0 is noted where it appears.

The core tag library holds the tags that do not depend on a render kit: they do not produce markup by themselves. Instead each one attaches something to a component or to the view: an attribute, a facet, a listener, a converter, a validator, a bundle of messages. That is why the library is called "core" and why its tags are always used together with the HTML tags of the h: library. The specification requires every implementation to provide the library with the URI jakarta.faces.core and says its tags must not render JavaScript or CSS of their own (section 9.4). Facelets declares it as:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core">

The Library at a Glance

Some core tags are covered by other pages because they belong to a larger topic. This page deals with the general-purpose tags in the table’s first group and points to the right page for the rest.

Table 1. Core tags by purpose
Tag What it does Where it is explained

f:attribute, f:param, f:facet

Attach a named attribute, a named parameter or a named facet to the closest parent component

This page

f:selectItem, f:selectItems, f:selectItemGroups

Build the option list of a select component

This page

f:view, f:metadata, f:subview, f:loadBundle

Describe the view as a whole: locale, phase hooks, metadata, naming scope, messages

This page

f:event, f:actionListener, f:setPropertyActionListener, f:resetValues

Register listeners and actions on a component

This page

f:viewParam, f:viewAction

Read request parameters and run view actions (declared inside f:metadata)

View Parameters and View Actions

f:convertNumber, f:convertDateTime, f:converter

Register converters

Converters

f:validateLength, f:validateLongRange, f:validateDoubleRange, f:validateRegex, f:validateRequired, f:validateBean, f:validateWholeBean, f:validator

Register validators

Validators and Bean Validation

f:ajax, f:websocket

Ajax behaviour and server push

Ajax and WebSocket push

f:passThroughAttribute, f:passThroughAttributes

Add HTML5 pass-through attributes

HTML5-Friendly Markup

f:importConstants

Expose the constants of a class to EL from the metadata

Expression Language

f:valueChangeListener, f:phaseListener

Register a value-change listener or a phase listener

Events and Listeners

Most of these tags are handled while the view is being built (the Restore View phase of every request, see the lifecycle page), a few act when an event happens later in the lifecycle, and a few are read at render time:

flowchart LR subgraph build["View build (Restore View)"] A["f:view, f:metadata, f:loadBundle"] B["f:attribute, f:facet, f:param, f:selectItems"] C["f:subview"] end subgraph events["Lifecycle events"] D["f:event (postAddToView, preValidate, postValidate)"] E["f:actionListener, f:setPropertyActionListener, f:resetValues"] end subgraph render["Render Response"] F["f:event (preRenderView, preRenderComponent)"] G["f:param and f:selectItems read by the renderer"] end A --> B --> C C --> D --> E --> F --> G

f:attribute

f:attribute stores a named value in the attribute map of the closest parent component (component.getAttributes()). Its two attributes are name and value, and both accept expressions. It is the tag for passing a small piece of information from the page to code that receives a component: an action listener, a validator, a converter or a custom renderer.

A literal value is stored as it is. An EL expression is stored on the component and evaluated each time the attribute is read, so inside an iteration each row sees its own value. The following page offers one link per book and tells the listener which book was picked without any request parameter:

attributes.xhtml
<h:form id="shelf">
  <ui:repeat id="rows" value="#{attributeBean.books}" var="book">
    <h:commandLink id="pick" value="Pick #{book.title}"
                   actionListener="#{attributeBean.select}">
      <f:attribute name="isbn" value="#{book.isbn}"/>
    </h:commandLink>
    <br/>
  </ui:repeat>
</h:form>
<p id="last">Last: #{attributeBean.lastIsbn}</p>
AttributeBean.java
@Named
@RequestScoped
public class AttributeBean {

    @Inject
    private BookService books;

    private String lastIsbn;

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

    public void select(ActionEvent event) {
        UIComponent source = event.getComponent();
        lastIsbn = (String) source.getAttributes().get("isbn");
    }

    public String getLastIsbn() {
        return lastIsbn;
    }
}

Posting the second link (shelf:rows:1:pick) produced Last: 9780547928227, the ISBN of the second book, on both implementations. In a real application prefer an EL method call with a parameter (action="#{attributeBean.open(book)}", shown under action listeners), which is typed and needs no cast. f:attribute remains the right tool when the receiver is generic code that only has the component, such as the validator below.

A validator that must know a limit chosen in the page reads it from the component it is validating:

year.xhtml
<h:form id="f">
  <h:inputText id="year" value="#{yearBean.year}" validator="bookshelf.yearRange">
    <f:attribute name="minYear" value="#{yearBean.floor}"/>
  </h:inputText>
  <h:message for="year"/>
  <h:commandButton id="go" value="Save"/>
</h:form>
YearRangeValidator.java
@FacesValidator("bookshelf.yearRange")
public class YearRangeValidator implements Validator<Integer> {

    @Override
    public void validate(FacesContext context, UIComponent component, Integer value) {
        int min = Integer.parseInt(String.valueOf(component.getAttributes().get("minYear")));
        if (value != null && value < min) {
            throw new ValidatorException(
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Year must be at least " + min, null));
        }
    }
}

Submitting 1850 with a floor of 1900 displayed Year must be at least 1900. The validator page covers @FacesValidator, validator methods and default validators in depth. One related detail seen in the scratch run: with the CDI mode annotated (the beans.xml of the Bookshelf), a validator declared as @FacesValidator(managed = true) was not found until it also carried a bean-defining annotation such as @Dependent; the error read "could not be resolved by CDI".

f:param

f:param adds a child UIParameter component (component type jakarta.faces.Parameter) to the closest parent component. The attributes are name, value, id, binding and disable; disable takes a boolean and leaves the parameter out when true. What the parameter means depends on the parent, because the parent’s renderer decides what to do with its UIParameter children:

  • Inside h:outputFormat an unnamed parameter is the next argument of a java.text.MessageFormat pattern.

  • Inside h:link, h:button and h:outputLink a named parameter becomes a query string pair, which is how bookmarkable links are built.

  • Inside h:commandLink a named parameter is sent with the postback as a request parameter.

The VDL documentation lists the Output, Link and OutcomeTarget Link renderers as the ones that consult the UIParameter children. The scratch run also showed h:commandButton carrying the parameter in its generated submit script on both implementations (a post with the parameter reached the bean on Mojarra), but that is implementation behaviour rather than a documented guarantee, so use a link or an EL method call when the value matters.

params.xhtml
<ui:repeat id="rows" value="#{paramBean.books}" var="book">
  <p>
    <h:outputFormat id="line" value="{0} by {1} ({2,number,#})">
      <f:param value="#{book.title}"/>
      <f:param value="#{book.author}"/>
      <f:param value="#{book.year}"/>
    </h:outputFormat>
    <h:link id="detail" value="Details" outcome="book">
      <f:param name="isbn" value="#{book.isbn}"/>
      <f:param name="tab" value="reviews" disable="#{book.year lt 1950}"/>
    </h:link>
    <h:form id="f">
      <h:commandLink id="borrowLink" value="Borrow" action="#{paramBean.borrow}">
        <f:param name="isbn" value="#{book.isbn}"/>
      </h:commandLink>
    </h:form>
  </p>
</ui:repeat>
ParamBean.java
@Named
@RequestScoped
public class ParamBean {

    @Inject
    private BookService books;

    @Inject
    @ManagedProperty("#{param.isbn}")
    private String isbn;

    private String message = "none";

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

    public String borrow() {
        message = "borrow requested for " + isbn;
        return null;
    }

    public String getMessage() {
        return message;
    }
}

The output of the first request was Dune by Frank Herbert (1965), and the link of the first book was book.xhtml?isbn=9780441013593&tab=reviews; for a book older than 1950 the tab pair disappeared because of disable. MyFaces writes the query pairs in a different order (tab before isbn for the third book), so never depend on the order. Posting the borrow link with isbn=9780547928227 printed borrow requested for 9780547928227. jakarta.faces.annotation.ManagedProperty injects the request parameter into a CDI bean property (@Inject is required with it).

Two details about h:outputFormat showed up in the run:

  • Arguments are formatted with the current locale by MessageFormat. The year 1965 printed as 1,965 with {2}; writing {2,number,#} prints 1965. If the value is an identifier such as a year or an ISBN, pass a string or use that pattern.

  • Apostrophes follow MessageFormat rules. With a parameter present, It's {0} printed Its {0} on both implementations, with the placeholder not even substituted; It''s {0} printed It's Dune. Without any parameter Mojarra left It's here alone while MyFaces printed Its here. In a resource bundle used with parameters, double every apostrophe.

f:facet

A facet is a named child component with a special role in its parent. Unlike ordinary children it is not part of the child list: the parent finds it by name (component.getFacet("header")) and decides where to render it. f:facet takes one required attribute, name. The standard HTML components use these facets:

Table 2. Facets of the standard HTML components
Component Facet names

h:dataTable

caption, header, footer, colgroups

h:column

header, footer

h:panelGrid

caption, header, footer

The caption facet is rendered inside a caption element below the table element, header and footer become thead and tfoot rows, and colgroups (data table only) holds one component that renders colgroup and col elements.

facets.xhtml
<h:dataTable id="tbl" value="#{facetBean.books}" var="book" border="1">
  <f:facet name="caption">Catalogue</f:facet>
  <f:facet name="header">
    <h:outputText value="All books"/>
    <h:outputText value=" (3)"/>
  </f:facet>
  <h:column>
    <f:facet name="header">Title</f:facet>
    #{book.title}
    <f:facet name="footer">end</f:facet>
  </h:column>
  <h:column>
    <f:facet name="header">Year</f:facet>
    #{book.year}
  </h:column>
  <f:facet name="footer">Source</f:facet>
</h:dataTable>
<h:panelGrid id="grid" columns="2">
  <f:facet name="header">Panel</f:facet>
  <h:outputText value="a"/>
  <h:outputText value="b"/>
</h:panelGrid>

The rendered HTML (whitespace trimmed) was:

<table id="tbl" border="1">
<caption>Catalogue</caption>
<thead>
<tr><th colspan="2" scope="colgroup">All books (3)</th></tr>
<tr><th scope="col">Title</th><th scope="col">Year</th></tr>
</thead>
<tfoot>
<tr><td>end</td><td></td></tr>
<tr><td colspan="2">Source</td></tr>
</tfoot>
<tbody> ... one tr per book ... </tbody>
</table>
<table id="grid">
<thead><tr><th colspan="2" scope="colgroup">Panel</th></tr></thead>
<tbody><tr><td>a</td><td>b</td></tr></tbody>
</table>

Notice the table-level header: the two h:outputText children of one f:facet rendered one after the other (All books (3)). The VDL says that when a facet has more than one child the implementation puts them in a container UIPanel automatically. The h:dataTable documentation still describes its colgroups facet as accepting one child, so wrap several elements yourself with a h:panelGroup when a renderer expects a single component. The data table page goes deeper into tables.

Facets also appear in composite components (cc:facet, cc:renderFacet, cc:insertFacet) and in custom components, where the component class reads them with getFacet(name) and the renderer calls encodeAll on them. Those uses are explained on the composite components and custom components pages.

f:selectItem, f:selectItems and f:selectItemGroups

The select components (h:selectOneMenu, h:selectManyCheckbox, h:selectOneRadio and the rest, see the page on HTML components) get their options from these three tags nested inside them.

  • f:selectItem adds one option. Its attributes are itemValue, itemLabel, itemDescription, itemDisabled, itemEscaped and noSelectionOption; or value with a jakarta.faces.model.SelectItem instance. binding and id are also available.

  • f:selectItems adds many options from a collection, array or DataModel in value, which may contain SelectItem objects or any plain object. For plain objects use var to name the current element and itemValue, itemLabel, itemDescription, itemDisabled and itemLabelEscaped to read its properties. A java.util.Map is also accepted: its keys are the labels and its values are the option values (as the sort options below show). noSelectionValue marks one element as the "no selection" option.

  • f:selectItemGroups (Faces 4.0 and later) adds groups: value and var iterate the groups, itemLabel labels the group (rendered as optgroup), itemDisabled disables it, and a nested f:selectItems or f:selectItem supplies the options of each group.

The escaping flag is named differently on the two tags: itemEscaped on f:selectItem and itemLabelEscaped on f:selectItems. In the scratch run, a label <b>two</b> with itemEscaped="false" on f:selectItem rendered as real bold markup, while itemLabelEscaped="false" on a single f:selectItem was ignored and the markup stayed escaped. Both implementations behaved alike. Leave escaping on (the default) for every label that includes user data.

selects.xhtml
<h:form id="f">
  <h:selectOneMenu id="genre" value="#{selectBean.genre}" required="true">
    <f:selectItem itemLabel="-- choose a genre --" noSelectionOption="true"/>
    <f:selectItems value="#{selectBean.genres}" var="g"
                   itemValue="#{g}" itemLabel="#{g.label}"/>
  </h:selectOneMenu>
  <h:message for="genre"/>

  <h:selectOneMenu id="book" value="#{selectBean.isbn}">
    <f:selectItem itemLabel="-- any book --" noSelectionOption="true"/>
    <f:selectItems value="#{selectBean.books}" var="b"
                   itemValue="#{b.isbn}" itemLabel="#{b.title} (#{b.year})"
                   itemDisabled="#{b.year lt 1940}"/>
  </h:selectOneMenu>

  <h:selectOneRadio id="sort" value="#{selectBean.sort}">
    <f:selectItems value="#{selectBean.sortOptions}"/>
  </h:selectOneRadio>

  <h:selectOneMenu id="related" value="#{selectBean.related}">
    <f:selectItems value="#{selectBean.bookItems}"/>
  </h:selectOneMenu>

  <h:selectOneMenu id="grouped" value="#{selectBean.related}">
    <f:selectItemGroups value="#{selectBean.genres}" var="g" itemLabel="#{g.label}">
      <f:selectItems value="#{selectBean.books}" var="b"
                     itemValue="#{b.isbn}" itemLabel="#{b.title}"/>
    </f:selectItemGroups>
  </h:selectOneMenu>
  <h:commandButton id="save" value="Save" action="#{selectBean.save}"/>
</h:form>
SelectBean.java (the parts the page uses)
@Named
@RequestScoped
public class SelectBean {

    @Inject
    private BookService books;

    private Genre genre;
    private String isbn;
    private String sort;
    private String related;

    public Genre[] getGenres() {
        return Genre.values();
    }

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

    public Map<String, String> getSortOptions() {
        Map<String, String> m = new LinkedHashMap<>();
        m.put("Title (A-Z)", "title");
        m.put("Year (newest first)", "year");
        return m;
    }

    public List<SelectItem> getBookItems() {
        return books.findAll().stream().map(b -> new SelectItem(b.isbn(), b.title())).toList();
    }

    // getters, setters and save() omitted
}
Genre.java
public enum Genre {
    FANTASY("Fantasy"), SCIENCE_FICTION("Science fiction"), CLASSIC("Classic");

    private final String label;

    Genre(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }
}

What the run showed:

  • The "choose a genre" item has no itemValue, so it renders value="". Because noSelectionOption="true" is set, a required menu left on it fails with Validation Error: Value is required. (verified on both implementations). Without the flag the empty value would be an ordinary option.

  • The third book (1937) is rendered with disabled="disabled". Posting its ISBN anyway did not change the model and showed no message on either implementation: the submitted value was ignored. A posted value that matches no option at all produced Validation Error: Value is not valid.

  • The enum menu uses itemValue="#{g}"; Faces applies its built-in enum converter, so the submitted FANTASY becomes Genre.FANTASY (the page on converters shows when to write your own for entities).

  • The grouped menu rendered one optgroup per genre. The same book list repeated in each group only because the demo has one list; in an application each group would carry its own items.

Where it sits in the lifecycle: the option list is evaluated when the menu is rendered, and again in Process Validations, where Faces checks that the submitted value is one of the options. A list that changes between the render request and the postback, because it depends on a request-scoped property or on other fields, makes valid submissions fail with "Value is not valid". Keep the list stable for the life of the view, for example by loading it once in a @ViewScoped bean or in a @PostConstruct method.

f:view

f:view is the root tag of a Facelets page. It is optional, because a UIViewRoot exists whether or not the tag is written, but it is the only place to set properties of the view in markup. All its attributes accept expressions:

Table 3. Attributes of f:view (Jakarta Faces 4.1 VDL)
Attribute Meaning

locale

Locale of the page: a java.util.Locale or a string converted to one

contentType

Content type of the response

encoding

Character encoding of the response

renderKitId

Identifier of the render kit used to render the view

beforePhase

Method void beforePhase(PhaseEvent) called before every phase except Restore View

afterPhase

Method void afterPhase(PhaseEvent) called after every phase except Restore View on an initial request

contracts

Comma-separated resource library contracts usable in the Facelets chain; only on the outermost file

transient

When true, the view takes no part in state saving (a stateless view)

f:view also holds f:metadata, which must be its direct child (see the next section). The tag appears in templates because the template is the file that owns the html element; the page that names the template contributes the content.

detail.xhtml, locale chosen from the query string
<f:view locale="#{localeBean.locale}" contentType="text/html" encoding="UTF-8">
  <f:metadata>
    <f:viewParam name="isbn" value="#{detailBean.isbn}" required="true"/>
    <f:viewParam name="lang" value="#{localeBean.lang}"/>
    <f:event type="preRenderView" listener="#{detailBean.load}"/>
  </f:metadata>
  <f:loadBundle basename="org.example.bookshelf.messages" var="msg"/>
  <h:head><title>#{msg['catalogue.title']}</title></h:head>
  <h:body>
    <h:outputFormat id="welcome" value="#{msg['catalogue.welcome']}">
      <f:param value="#{detailBean.book.title}"/>
    </h:outputFormat>
  </h:body>
</f:view>
LocaleBean.java
@Named
@RequestScoped
public class LocaleBean {

    private String lang = "en";

    public String getLang() {
        return lang;
    }

    public void setLang(String lang) {
        this.lang = lang;
    }

    public Locale getLocale() {
        return Locale.forLanguageTag(lang);
    }
}

Requesting detail.xhtml?isbn=9780441013593 returned Welcome to the Bookshelf, Dune; adding &lang=es returned Bienvenido a la Bookshelf, Dune and a Spanish title, on both implementations. The locale expression is read when the view is rendered, after the view parameters were applied, which is why a view parameter can drive it. The response header was Content-Type: text/html;charset=UTF-8. A request without isbn fails the required check and, in the Development project stage, shows the message at the foot of the page.

beforePhase and afterPhase give one view its own phase hooks without a global PhaseListener:

phases.xhtml
<f:view beforePhase="#{phaseBean.before}" afterPhase="#{phaseBean.after}">
  <h:head><title>Phases</title></h:head>
  <h:body>
    <h:form id="f">
      <h:inputText id="n" value="#{phaseBean.name}"/>
      <h:commandButton id="go" value="Go" action="#{phaseBean.go}"/>
    </h:form>
    <p id="trace">#{phaseBean.trace}</p>
  </h:body>
</f:view>
PhaseBean.java (hooks)
public void before(PhaseEvent event) {
    TraceBean.add("before " + event.getPhaseId());
}

public void after(PhaseEvent event) {
    TraceBean.add("after " + event.getPhaseId());
}

On a postback the trace read after RESTORE_VIEW | before APPLY_REQUEST_VALUES | after APPLY_REQUEST_VALUES | before PROCESS_VALIDATIONS | after PROCESS_VALIDATIONS | before UPDATE_MODEL_VALUES | after UPDATE_MODEL_VALUES | before INVOKE_APPLICATION | after INVOKE_APPLICATION | before RENDER_RESPONSE: there is no before RESTORE_VIEW entry, exactly as the VDL describes. The first request of the page showed only after RESTORE_VIEW and before RENDER_RESPONSE. (TraceBean is a static helper that collects the strings. The trace is printed while the page is rendered, so an after RENDER_RESPONSE entry can only show up in the page of the next request.)

transient="true" makes a stateless view: the hidden jakarta.faces.ViewState field was rendered with the value stateless on both implementations, and no server-side view state is kept. The VDL warns that transient views cannot be used with @ViewScoped beans. The page on state saving explains the trade-offs.

f:metadata

f:metadata declares the view metadata: the things that describe how a view is entered and that must be processed before the whole component tree is built. It holds f:viewParam (read and convert request parameters), f:viewAction (run a method on the initial request) and f:event (system events). The specification describes how, with view metadata, the Restore View phase can create a metadata-only view first; it is also what lets h:link and h:button build bookmarkable URLs for a target view (specification section 2.5.6, Bookmarkability).

The VDL fixes the placement rules:

  • it must be a direct child of f:view;

  • it must be in the top-level file of the view or in a template client, never in a template;

  • the implementation wraps its children in a UIPanel stored as a facet of the view root under the name UIViewRoot.METADATA_FACET_NAME (in Faces 4.1 the constant is jakarta_faces_metadata).

To keep f:view in a template while each page declares its own metadata, the template exposes a slot for it:

WEB-INF/templates/page.xhtml
<!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>
  <ui:insert name="metadata"/>
  <h:head><title><ui:insert name="title">Bookshelf</ui:insert></title></h:head>
  <h:body>
    <ui:insert name="content"/>
  </h:body>
</f:view>
</html>
tpl-good.xhtml, a template client
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:f="jakarta.faces.core"
                xmlns:ui="jakarta.faces.facelets"
                template="/WEB-INF/templates/page.xhtml">
  <ui:define name="metadata">
    <f:metadata>
      <f:viewParam name="isbn" value="#{detailBean.isbn}"/>
      <f:event type="preRenderView" listener="#{detailBean.load}"/>
    </f:metadata>
  </ui:define>
  <ui:define name="content">
    <p id="out">isbn=#{detailBean.isbn} title=#{detailBean.book.title}</p>
  </ui:define>
</ui:composition>

Requesting tpl-good.xhtml?isbn=9780441013593 printed isbn=9780441013593 title=Dune on Mojarra and MyFaces. Moving the same f:metadata block into the template itself (bad.xhtml in the run) printed isbn= title= on both: the parameter was never read and no error or warning was raised. If a view parameter seems to be ignored, check that the metadata is not in a template. The page on view parameters and view actions explains the parameters themselves in depth. See the templating page for ui:insert and ui:define.

f:event

f:event installs a ComponentSystemEventListener on the closest parent component for one system event. Attributes:

  • type (required): the event name or a fully qualified class name.

  • listener (required): a method expression. The method may take a jakarta.faces.event.ComponentSystemEvent and return void, or take no arguments and return void; in the second form the method cannot tell which component fired.

Table 4. Values of type (Jakarta Faces 4.1 VDL)
type Event class Fired

postAddToView

PostAddToViewEvent

After the component was added to the view (while the view is built)

preValidate

PreValidateEvent

Before the component and its children are validated (Process Validations)

postValidate

PostValidateEvent

After the component and its children were validated (Process Validations)

preRenderView

PreRenderViewEvent

Before the view is rendered

preRenderComponent

PreRenderComponentEvent

Before the component is rendered (Render Response)

Any class that extends ComponentSystemEvent can be named in type by its fully qualified name. If the class is annotated with @NamedEvent, its shortName is usable as the type.

detail.xhtml (events)
<h:outputText id="t1" value="x">
  <f:event type="postAddToView" listener="#{detailBean.added}"/>
  <f:event type="preRenderComponent" listener="#{detailBean.before}"/>
  <f:event type="preRenderComponent" listener="#{detailBean.noArgs}"/>
</h:outputText>
DetailBean.java (listeners)
public void load() {
    log.add("preRenderView (postback=" + facesContext.isPostback() + ")");
    book = books.findByIsbn(isbn);
}

public void added(ComponentSystemEvent event) {
    log.add("postAddToView " + event.getComponent().getId());
}

public void before(ComponentSystemEvent event) {
    log.add("preRenderComponent " + event.getComponent().getId());
}

public void noArgs() {
    log.add("no-arg listener");
}

The log printed on the page, with the preRenderView listener from the metadata above, was:

[postAddToView t1, preRenderView (postback=false), preRenderComponent t1, no-arg listener]

so the events arrive in this order: component added (build), view about to render, component about to render. Mojarra and MyFaces gave the same order.

preRenderView is the traditional hook for loading data once the view parameters are applied, but it fires on every request that renders the view, postbacks included, which is why the example prints isPostback(). f:viewAction is usually better for initial-request logic because it can navigate and can be limited to the initial request; see its page.

postValidate on a container is the standard way to do validation that involves several fields. The listener runs after the container’s children were validated and so can read their values and mark the view as failed:

borrow.xhtml
<h:form id="f">
  <h:panelGroup id="range">
    <h:inputText id="from" value="#{borrowBean.fromDay}" required="true"/>
    <h:inputText id="to" value="#{borrowBean.toDay}" required="true"/>
    <f:event type="postValidate" listener="#{borrowBean.checkRange}"/>
  </h:panelGroup>
  <h:messages id="msgs"/>
  <h:commandButton id="go" value="Borrow" action="#{borrowBean.submit}"/>
</h:form>
BorrowBean.java
public void checkRange(ComponentSystemEvent event) {
    FacesContext context = FacesContext.getCurrentInstance();
    UIComponent group = event.getComponent();
    UIInput from = (UIInput) group.findComponent("from");
    UIInput to = (UIInput) group.findComponent("to");
    if (!from.isValid() || !to.isValid()) {
        return;
    }
    Integer start = (Integer) from.getValue();
    Integer end = (Integer) to.getValue();
    if (start != null && end != null && end < start) {
        context.addMessage(to.getClientId(context),
                new FacesMessage(FacesMessage.SEVERITY_ERROR,
                        "Return day must not be before the start day", null));
        context.validationFailed();
        context.renderResponse();
    }
}

Posting from=10 and to=5 re-displayed the form with Return day must not be before the start day and did not call submit(); from=5, to=10 called it. The check on isValid() matters: a failed conversion or required check leaves the local value unset. The page on validators compares this with f:validateWholeBean and Bean Validation (Bean Validation page).

A custom event is a class that extends ComponentSystemEvent and carries @NamedEvent:

BookAddedEvent.java and its use
@NamedEvent(shortName = "bookAdded")
public class BookAddedEvent extends ComponentSystemEvent {

    public BookAddedEvent(UIComponent component) {
        super(component);
    }
}

// somewhere that runs during the request, for example another f:event listener:
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, BookAddedEvent.class, event.getComponent());
<h:outputText id="shelf" value="shelf">
  <f:event type="bookAdded" listener="#{customEventBean.heard}"/>
  <f:event type="org.example.bookshelf.web.BookAddedEvent" listener="#{customEventBean.heard}"/>
  <f:event type="preRenderComponent" listener="#{customEventBean.fire}"/>
</h:outputText>

Both the short name and the class name reached the listener (bookAdded heard by shelf twice) on both implementations.

f:loadBundle

f:loadBundle loads a ResourceBundle and exposes it as a java.util.Map in the request scope. Attributes: basename (the bundle base name) and var (required, the name of the request attribute). The bundle is looked up for the locale of the current view, so f:view locale controls it. This is the page-local way to reach messages; the application-wide alternative is <resource-bundle> in faces-config.xml, which the page on messages and internationalisation covers.

bundle.xhtml
<f:loadBundle basename="org.example.bookshelf.messages" var="msg"/>
<p id="after">[#{msg['catalogue.title']}] [#{msg['catalogue.welcome']}]</p>
<h:outputFormat id="w" value="#{msg['catalogue.welcome']}">
  <f:param value="Alberto"/>
</h:outputFormat>
<h:outputText id="s" value="#{msg['book.title']}"/>
org/example/bookshelf/messages.properties and messages_es.properties
# messages.properties
catalogue.title=Catalogue
catalogue.welcome=Welcome to the Bookshelf, {0}
book.title=Title

# messages_es.properties
catalogue.title=Catalogo
catalogue.welcome=Bienvenido a la Bookshelf, {0}
book.title=Titulo

The output was [Catalogue] [Welcome to the Bookshelf, {0}], Welcome to the Bookshelf, Alberto and Title, identical on both implementations. Things the run confirmed:

  • A missing key does not throw: the Map returns ???no.such.key???, as the VDL requires.

  • A missing bundle does. Mojarra reported Can't find bundle for base name org.example.bookshelf.nope, locale en_US (the VDL says a TagAttributeException must be thrown).

  • Keys that contain dots need the bracket form: #{msg['catalogue.title']} works, while #{msg.catalogue.title} failed with Property [title] not found on type [java.lang.String] because EL read msg.catalogue as a string.

  • An expression such as #{msg['catalogue.title']} written above the f:loadBundle tag in the same page still resolved. The tag runs while the view is built and the text is evaluated at render time, so the Map is already in place. Attributes that are evaluated at build time are a different matter, so declare the bundle at the top.

  • ResourceBundle falls back to the JVM default locale before the base file. With a Spanish default locale and no messages_en.properties, an English request showed the Spanish text. Provide the bundle for each locale you expect, or set -Duser.language consistently.

f:subview

f:subview is a naming container tag: it creates a UINamingContainer that prefixes the client ids of everything inside it. Attributes: id (required), rendered and binding. The VDL says it is useful when a section that is included with ui:include (or any tag that includes another page dynamically) appears more than once in a view and its component ids would collide.

Including the same file twice inside one form fails, because the component ids inside the file are not unique:

WEB-INF/includes/search-box.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="jakarta.faces.html"
                xmlns:ui="jakarta.faces.facelets">
  <h:inputText id="query" value="#{subBean.query}"/>
</ui:composition>
subview-bad.xhtml, fails
<h:form id="f">
  <ui:include src="/WEB-INF/includes/search-box.xhtml"/>
  <ui:include src="/WEB-INF/includes/search-box.xhtml"/>
</h:form>
subview-good.xhtml
<h:form id="f">
  <f:subview id="left"><ui:include src="/WEB-INF/includes/search-box.xhtml"/></f:subview>
  <f:subview id="right"><ui:include src="/WEB-INF/includes/search-box.xhtml"/></f:subview>
</h:form>

The first page failed on Mojarra with IllegalStateException: Component ID f:query has already been found in the view and on MyFaces with DuplicateIdException: Component with duplicate id "f:query" found. The second rendered two inputs with the ids f:left:query and f:right:query. The same file bound to the same property twice is only a demonstration of the id rule; give each section its own value expression, for example through ui:param, in a real page.

Where f:subview was once the standard tool to keep JSP includes apart, Facelets has better ones: a composite component is already a naming container, and ui:repeat and h:dataTable each create one per row. Use f:subview for the cases that remain. The JSF 2.0 change log of the specification lists a request (issue 852) to mark f:subview and f:verbatim as deprecated in the VDL documentation; the 4.1 VDL page of f:subview carries no deprecation note.

f:verbatim

In JSF 1.x views written as JSP pages, template text and HTML around Faces tags was written to the response before the component tree existed, so it appeared out of order. f:verbatim wrapped such text in a component so that it stayed in place. Facelets builds the tree from the XML and never has the problem, and JSP views were removed in Faces 4.0.

f:verbatim has no page in the 4.1 VDL documentation. A page that used it:

verbatim.xhtml
<f:verbatim><b>hello</b></f:verbatim>

failed to build on both implementations: Mojarra reported Tag Library supports namespace: jakarta.faces.core, but no tag was defined for name: verbatim and MyFaces printed the same sentence. Delete the tag and write the markup directly. If text must be escaped, use h:outputText; if it must not be escaped (trusted content only), use h:outputText with escape="false", which carries the same cross-site-scripting risk as before.

Source: Jakarta Faces 4.1 VDL: f: tag library summary (it has no f:verbatim entry)

f:resetValues

When a form fails validation, each input keeps the submitted value, so a "Cancel" button that merely re-renders the page shows the invalid text again. UIViewRoot.resetValues(FacesContext, Collection<String>) clears the submitted and local values of the components with the given client ids so that they show the model values again. f:resetValues is the tag that calls it from an action. It has one attribute, render, a space-separated list of client ids, and it registers an action listener on the closest ActionSource.

Both implementations register the tag (ResetValuesHandler in Mojarra, ResetValuesActionListenerHandler in MyFaces), but it has no entry in the 4.1 VDL documentation, so treat it as a convenience provided by both implementations rather than a portable tag. f:ajax has a documented equivalent, the resetValues attribute, whose render list is likewise "a space separated list of client identifiers suitable for passing directly to UIViewRoot.resetValues()`" (VDL); the Ajax page covers it. Faces 5.0 is planned to add a `clearModel attribute to f:resetValues (issue 1940 on the specification project, listed on the 5.0 specification page): the issue describes it as the counterpart of PrimeFaces' clearModel flag, which does not only reset the component state but also sets the model value to null. It does not exist in 4.1 and nothing here relies on it; the planned list is on the versions page.

reset.xhtml
<h:form id="f">
  <h:panelGroup id="fields" layout="block">
    <h:inputText id="reviewer" value="#{reviewBean.reviewer}" required="true"/>
    <h:inputText id="rating" value="#{reviewBean.rating}"/>
    <h:messages/>
  </h:panelGroup>
  <h:commandButton id="save" value="Save" action="#{reviewBean.save}"/>
  <h:commandButton id="cancel" value="Cancel" immediate="true">
    <f:resetValues render="f:fields"/>
  </h:commandButton>
  <h:commandButton id="plain" value="Cancel plain" immediate="true"/>
</h:form>

With an empty reviewer and abc in rating posted:

  • save re-displayed both invalid values with a "Value is required" and a conversion message;

  • plain re-displayed abc and the empty text, no messages;

  • cancel re-displayed anonymous and 3, the model values.

The ids must be the full client ids: render="f:fields" worked, while render="fields" and render="@form" changed nothing, on both implementations. The button is immediate="true" so that it runs in Apply Request Values and skips validation (the lifecycle consequences of immediate are on the lifecycle page).

Source: API: UIViewRoot.resetValues and, for the planned clearModel, Jakarta Faces 5.0 (under development) with issue 1940

f:actionListener and f:setPropertyActionListener

A command component (h:commandButton, h:commandLink) can run three kinds of code when it is activated:

  1. its actionListener attribute, a method that receives the ActionEvent;

  2. action listeners registered as child tags: f:actionListener and f:setPropertyActionListener;

  3. its action attribute, a method that returns the navigation outcome.

f:actionListener registers an ActionListener implementation, either by class with type (a fully qualified name) or by instance with binding (an expression that evaluates to a listener). for targets a nested attached object inside a composite component. f:setPropertyActionListener copies a value into a property when the command fires: value is the source expression (required) and target is the destination expression (required); the value is coerced to the type of the target. Both are ActionSource listeners: they work on h:commandButton and h:commandLink and not on h:link/h:button, which are navigation components.

listeners.xhtml
<h:form id="f">
  <ui:repeat id="rows" value="#{listenerBean.books}" var="book">
    <h:commandButton id="open" value="Open #{book.title}" action="#{listenerBean.open}"
                     actionListener="#{listenerBean.track}">
      <f:actionListener type="org.example.bookshelf.web.AuditListener"/>
      <f:actionListener binding="#{auditListener}"/>
      <f:setPropertyActionListener target="#{listenerBean.selected}" value="#{book}"/>
      <f:setPropertyActionListener target="#{listenerBean.selectedIsbn}" value="#{book.isbn}"/>
    </h:commandButton>
    <h:commandButton id="open2" value="Open with a method call"
                     action="#{listenerBean.openBook(book)}"/>
  </ui:repeat>
</h:form>
AuditListener.java
@Named
@RequestScoped
public class AuditListener implements ActionListener {

    @Inject
    private BookService books;

    @Override
    public void processAction(ActionEvent event) throws AbortProcessingException {
        TraceBean.add("f:actionListener " + getClass().getSimpleName()
                + " on " + event.getComponent().getId());
    }
}
ListenerBean.java (the parts used)
public void track(ActionEvent event) {
    TraceBean.add("actionListener attribute, selected=" + selected);
}

public String open() {
    TraceBean.add("action, selected=" + selected + ", isbn=" + selectedIsbn);
    return null;
}

public String openBook(Book book) {
    TraceBean.add("openBook(" + book.title() + ")");
    return null;
}

Posting the second open button gave, identically on Mojarra and MyFaces:

actionListener attribute, selected=null
f:actionListener AuditListener on open   (injected=false, created from type)
f:actionListener AuditListener on open   (injected=true, resolved through binding)
action, selected=Book[isbn=9780547928227, title=The Hobbit, ...], isbn=9780547928227

So the order is: the actionListener attribute first, then the registered listeners in the order of the tags (the property setters fill selected and selectedIsbn here), then action, which therefore already sees the selected values. Two points to keep in mind:

  • A listener created from type is instantiated by Faces with its no-argument constructor and was not injected, even though the same class is a CDI bean. Use binding with a bean when the listener needs injection.

  • An action listener cannot influence navigation (it returns nothing). Put logic that decides the outcome in action.

Posting open2 (the EL method call) printed openBook(The Fellowship of the Ring) on both. The EL form action="#{listenerBean.openBook(book)}" is typed, needs no extra tag, and has been available since EL 2.2 method parameters in the Facelets expression language (the page on the Expression Language covers it); prefer it to f:setPropertyActionListener in new code. f:setPropertyActionListener still has its place when the target must be set before another listener runs, or when the code is written for an EL implementation without method parameters.

A listener has one more limit worth knowing: with immediate="true" on the command, its listeners run in Apply Request Values, before conversion and validation of the other inputs, so the properties they set can hold values that were never validated. See the lifecycle page and the page on events and listeners for AbortProcessingException and ActionEvent details.

What Changed

  • JSF 2.0: f:event and the system-event model, f:metadata (the view metadata facet), the new f:selectItems attributes var, itemValue, itemLabel, itemDescription, itemDisabled and itemLabelEscaped, and Ajax. The VDL documentation of f:selectItems still says "Version 2 of the specification introduces several new attributes".

  • Faces 2.2 and 2.3: f:viewAction and the pass-through tags (2.2); f:websocket and f:importConstants (2.3).

  • Faces 3.0: the move from javax.faces to jakarta.faces packages. The tag URIs stayed as they were.

  • Faces 4.0: the tag URI jakarta.faces.core replaced http://xmlns.jcp.org/jsf/core; JSP views, and with them the need for f:verbatim, were removed; f:selectItemGroups was added.

  • Faces 4.1: no new core tag. The old namespace URIs are slated for removal in the upcoming Faces 5.0; see the old namespaces in the Facelets overview.

  • Faces 5.0: upcoming and still a milestone; do not rely on it. Planned for this library: a clearModel attribute on f:resetValues (see the f:resetValues section).

Where the Books Are Outdated

  • javax.faces and the 1.x namespace. The older books use http://java.sun.com/jsf/core and Java packages under javax.faces. Use jakarta.faces.core and jakarta.faces.* packages.

  • JSP views and f:verbatim. The JSF 1.x books show f:view as a mandatory wrapper of every JSP page, f:verbatim to keep template text in order, f:subview to separate included JSP files and JSP custom tags for the HTML components. With Facelets none of these is needed, and the tag f:verbatim is gone (see above).

  • Managed beans. Examples with f:setPropertyActionListener and f:param in books of that era target @ManagedBean or faces-config-declared beans, which Faces 4.0 removed. Use CDI @Named beans, and @ManagedProperty("#{param.isbn}") from jakarta.faces.annotation for a request parameter.

  • Select items. The 1.x idiom builds SelectItem lists in the bean. That still works (the bookItems menu above), but from JSF 2.0 f:selectItems reads plain objects directly with var, itemValue and itemLabel.

  • Binding and listeners. JSF 1.x method bindings (MethodBinding) are gone; the listener attribute of f:event and the actionListener attribute take method expressions.

  • Missing topics. The older books predate f:event, f:metadata, f:selectItemGroups and f:resetValues. Use the Jakarta EE Tutorial, the specification and the VDL documentation for them.

Common Mistakes

  • f:metadata in a template. It is ignored without a warning. Put it in the template client, inside a slot of f:view.

  • A select option list that changes between render and postback. The postback fails with "Value is not valid". Load the list once for the life of the view.

  • Passing numbers or ISBNs through h:outputFormat without a pattern. They are formatted for the locale (1,965).

  • An unescaped apostrophe in a message used with f:param. The text and its placeholder are mangled. Double it.

  • escape flags set to false on labels built from user data. Labels of f:selectItems and h:outputText are escaped by default; keep it that way.

  • Using f:attribute or f:setPropertyActionListener to carry secrets or authorisation decisions. The values travel through the view; decide permissions on the server from the authenticated principal.

  • State-changing h:link. Links built with f:param perform a GET. Use a command component with a post for changes.

  • Treating f:resetValues as portable. It is provided by both implementations but is not in the 4.1 VDL.

  • Assuming f:event type="preRenderView" runs only once. It runs on every request that renders the view.