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.
| Tag | What it does | Where it is explained |
|---|---|---|
|
Attach a named attribute, a named parameter or a named facet to the closest parent component |
This page |
|
Build the option list of a select component |
This page |
|
Describe the view as a whole: locale, phase hooks, metadata, naming scope, messages |
This page |
|
Register listeners and actions on a component |
This page |
|
Read request parameters and run view actions (declared inside |
|
|
Register converters |
|
|
Register validators |
|
|
Ajax behaviour and server push |
Ajax and WebSocket push |
|
Add HTML5 pass-through attributes |
|
|
Expose the constants of a class to EL from the metadata |
|
|
Register a value-change listener or a phase listener |
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:
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:
<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>
@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:
<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>
@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:outputFormatan unnamed parameter is the next argument of ajava.text.MessageFormatpattern. -
Inside
h:link,h:buttonandh:outputLinka named parameter becomes a query string pair, which is how bookmarkable links are built. -
Inside
h:commandLinka 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.
<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>
@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 year1965printed as1,965with{2}; writing{2,number,#}prints1965. If the value is an identifier such as a year or an ISBN, pass a string or use that pattern. -
Apostrophes follow
MessageFormatrules. With a parameter present,It's {0}printedIts {0}on both implementations, with the placeholder not even substituted;It''s {0}printedIt's Dune. Without any parameter Mojarra leftIt's herealone while MyFaces printedIts 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:
| Component | Facet names |
|---|---|
|
|
|
|
|
|
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.
<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:selectItemadds one option. Its attributes areitemValue,itemLabel,itemDescription,itemDisabled,itemEscapedandnoSelectionOption; orvaluewith ajakarta.faces.model.SelectIteminstance.bindingandidare also available. -
f:selectItemsadds many options from a collection, array orDataModelinvalue, which may containSelectItemobjects or any plain object. For plain objects usevarto name the current element anditemValue,itemLabel,itemDescription,itemDisabledanditemLabelEscapedto read its properties. Ajava.util.Mapis also accepted: its keys are the labels and its values are the option values (as the sort options below show).noSelectionValuemarks one element as the "no selection" option. -
f:selectItemGroups(Faces 4.0 and later) adds groups:valueandvariterate the groups,itemLabellabels the group (rendered asoptgroup),itemDisableddisables it, and a nestedf:selectItemsorf:selectItemsupplies 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.
<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>
@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
}
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 rendersvalue="". BecausenoSelectionOption="true"is set, a required menu left on it fails withValidation 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 producedValidation Error: Value is not valid. -
The enum menu uses
itemValue="#{g}"; Faces applies its built-in enum converter, so the submittedFANTASYbecomesGenre.FANTASY(the page on converters shows when to write your own for entities). -
The
groupedmenu rendered oneoptgroupper 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:
| Attribute | Meaning |
|---|---|
|
Locale of the page: a |
|
Content type of the response |
|
Character encoding of the response |
|
Identifier of the render kit used to render the view |
|
Method |
|
Method |
|
Comma-separated resource library contracts usable in the Facelets chain; only on the outermost file |
|
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.
<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>
@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:
<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>
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
UIPanelstored as a facet of the view root under the nameUIViewRoot.METADATA_FACET_NAME(in Faces 4.1 the constant isjakarta_faces_metadata).
To keep f:view in a template while each page declares its own metadata, the template exposes a slot for 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">
<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>
<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 ajakarta.faces.event.ComponentSystemEventand returnvoid, or take no arguments and returnvoid; in the second form the method cannot tell which component fired.
| type | Event class | Fired |
|---|---|---|
|
|
After the component was added to the view (while the view is built) |
|
|
Before the component and its children are validated (Process Validations) |
|
|
After the component and its children were validated (Process Validations) |
|
|
Before the view is rendered |
|
|
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.
<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>
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:
<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>
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:
@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.
Source: Jakarta Faces 4.1 VDL: f:event and API: NamedEvent
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.
<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']}"/>
# 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 aTagAttributeExceptionmust be thrown). -
Keys that contain dots need the bracket form:
#{msg['catalogue.title']}works, while#{msg.catalogue.title}failed withProperty [title] not found on type [java.lang.String]because EL readmsg.catalogueas a string. -
An expression such as
#{msg['catalogue.title']}written above thef:loadBundletag 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. -
ResourceBundlefalls back to the JVM default locale before the base file. With a Spanish default locale and nomessages_en.properties, an English request showed the Spanish text. Provide the bundle for each locale you expect, or set-Duser.languageconsistently.
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:
<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>
<h:form id="f">
<ui:include src="/WEB-INF/includes/search-box.xhtml"/>
<ui:include src="/WEB-INF/includes/search-box.xhtml"/>
</h:form>
<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.
Source: Jakarta Faces 4.1 VDL: f:subview
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:
<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.
<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:
-
savere-displayed both invalid values with a "Value is required" and a conversion message; -
plainre-displayedabcand the empty text, no messages; -
cancelre-displayedanonymousand3, 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:
-
its
actionListenerattribute, a method that receives theActionEvent; -
action listeners registered as child tags:
f:actionListenerandf:setPropertyActionListener; -
its
actionattribute, 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.
<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>
@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());
}
}
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
typeis instantiated by Faces with its no-argument constructor and was not injected, even though the same class is a CDI bean. Usebindingwith 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:eventand the system-event model,f:metadata(the view metadata facet), the newf:selectItemsattributesvar,itemValue,itemLabel,itemDescription,itemDisabledanditemLabelEscaped, and Ajax. The VDL documentation off:selectItemsstill says "Version 2 of the specification introduces several new attributes". -
Faces 2.2 and 2.3:
f:viewActionand the pass-through tags (2.2);f:websocketandf:importConstants(2.3). -
Faces 3.0: the move from
javax.facestojakarta.facespackages. The tag URIs stayed as they were. -
Faces 4.0: the tag URI
jakarta.faces.corereplacedhttp://xmlns.jcp.org/jsf/core; JSP views, and with them the need forf:verbatim, were removed;f:selectItemGroupswas 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
clearModelattribute onf:resetValues(see thef:resetValuessection).
Where the Books Are Outdated
-
javax.facesand the 1.x namespace. The older books usehttp://java.sun.com/jsf/coreand Java packages underjavax.faces. Usejakarta.faces.coreandjakarta.faces.*packages. -
JSP views and
f:verbatim. The JSF 1.x books showf:viewas a mandatory wrapper of every JSP page,f:verbatimto keep template text in order,f:subviewto separate included JSP files and JSP custom tags for the HTML components. With Facelets none of these is needed, and the tagf:verbatimis gone (see above). -
Managed beans. Examples with
f:setPropertyActionListenerandf:paramin books of that era target@ManagedBeanor faces-config-declared beans, which Faces 4.0 removed. Use CDI@Namedbeans, and@ManagedProperty("#{param.isbn}")fromjakarta.faces.annotationfor a request parameter. -
Select items. The 1.x idiom builds
SelectItemlists in the bean. That still works (thebookItemsmenu above), but from JSF 2.0f:selectItemsreads plain objects directly withvar,itemValueanditemLabel. -
Binding and listeners. JSF 1.x method bindings (
MethodBinding) are gone; thelistenerattribute off:eventand theactionListenerattribute take method expressions. -
Missing topics. The older books predate
f:event,f:metadata,f:selectItemGroupsandf:resetValues. Use the Jakarta EE Tutorial, the specification and the VDL documentation for them.
Common Mistakes
-
f:metadatain a template. It is ignored without a warning. Put it in the template client, inside a slot off: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:outputFormatwithout 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. -
escapeflags set to false on labels built from user data. Labels off:selectItemsandh:outputTextare escaped by default; keep it that way. -
Using
f:attributeorf:setPropertyActionListenerto 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 withf:paramperform a GET. Use a command component with a post for changes. -
Treating
f:resetValuesas 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.