Standard HTML Components
|
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, Java SE 17 or
later), with Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4. Faces 4.0 (Jakarta EE 10) behaves the same for
everything below except where What Changed Between Versions says otherwise. The code and the HTML shown were run for real: the pages were
deployed in a scratch Bookshelf WAR on Apache Tomcat 11.0.26 with Weld 6.0.4.Final (Java 21, English locale, project
stage Development), requested with curl, and the markup quoted is what came back, with session ids removed
(checked on 2026-10-08). Every statement about the tags is taken from the Jakarta Faces 4.1 VDL documentation, the
specification and the Jakarta EE Tutorial, and each example below is followed by the page it derives from.
The standard HTML tag library is the set of tags in the jakarta.faces.html namespace, conventionally bound to the
prefix h. Each tag creates one component in the view’s component tree and attaches the renderer that writes the
HTML; the components are explained in
the architecture page. The h tags are the vocabulary of
every Faces page, and the component libraries covered later in this section (PrimeFaces, OmniFaces) build on them. The
f namespace (jakarta.faces.core) tags that appear next to them, such as f:selectItems and f:param, belong to
the core tag library, covered on the core tags page; the ones needed here are explained where they are
used.
How an HTML Component Tag Works
A tag such as <h:inputText id="reviewer" value="#{reviewBean.reviewer}"/> does four things:
-
While the view is built, Facelets creates a
HtmlInputTextcomponent, copies the tag’s attributes into it (as values or as value expressions) and adds it to the parent in the component tree. -
While the request is decoded, the component’s renderer reads the request parameter named after the component’s client id and stores it as the component’s submitted value.
-
The submitted value is converted, validated and, if valid, written to the bean property the
valueexpression points at. -
While the response is rendered, the renderer writes the HTML element for the component’s current value.
The tag name therefore selects a component class and a renderer. Tags that look alike can share the behaviour and
differ only in the renderer: h:selectOneMenu, h:selectOneListbox and h:selectOneRadio are three tags over the
UISelectOne model with three renderers (Menu, Listbox and Radio). The following table is the whole library. The "Component" column names the class in the package
jakarta.faces.component.html and the last column gives what the Mojarra 4.1.17 run wrote.
| Tag | Component | HTML it renders |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a single |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
the escaped text, wrapped in a |
|
|
text built with |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Attributes Shared by Most Tags
Most h tags accept the same families of attributes. The VDL documentation lists them per tag; this is what they mean
and where they act in the request.
| Attribute | Meaning |
|---|---|
|
Names the component inside its naming container. Without one, Faces generates an id ( |
|
A boolean expression. When it is |
|
Stores the component instance in a bean property. See
the pitfalls of |
|
For output components the value to display; for input components the model value that is read for display and written when the input is valid. A value expression that is writable is required on an input. |
|
Converts between the string in the request and the Java type of the model value (converters page). |
|
Validation of the submitted value (validators page, Bean Validation). |
|
Moves conversion, validation and, for a command, the action to the Apply Request Values phase. See the lifecycle page for what it skips. |
|
A human name for an input; it replaces the client id at the start of the validation and conversion messages (messages). |
|
Rendered as the HTML attributes. On the server, a disabled or read-only input is skipped when decoding, so a request that is forged to send a value for it is ignored (lifecycle position). |
|
Pass-through attributes. |
|
Pass-through JavaScript. They are plain browser events, not Faces events; Ajax behaviours are attached with |
Any other attribute can be added to an h tag as an HTML5 pass-through attribute with the jakarta.faces.passthrough
namespace; this is covered on the HTML5-friendly markup
page. Only autocomplete="off" is written by the renderer for the attribute autocomplete: the VDL says any other
value renders nothing, and both implementations confirmed it (autocomplete="name" on h:inputText produced no
attribute, autocomplete="off" did).
Forms: h:form
Every input and every command component must sit inside an h:form. The form is the unit of submission: only the
components in the submitted form are decoded, converted, validated and updated, which is why a page often has several
small forms.
<h:form id="review">
...
</h:form>
The renderer wrote this for the form above:
<form id="review" name="review" method="post" action="/bookshelf/inputs.xhtml"
enctype="application/x-www-form-urlencoded">
<input type="hidden" name="review" value="review" />
...
<input type="hidden" name="jakarta.faces.ViewState" id="j_id1:jakarta.faces.ViewState:0"
value="..." autocomplete="one-time-code" />
</form>
Three things matter in that markup:
-
methodis alwayspost; Faces forms are postbacks to the same view (actionis the view’s own URL). -
The hidden field named after the form id tells Faces, while decoding, which form was submitted. MyFaces uses a different convention: it writes
<input type="hidden" name="review_SUBMIT" value="1" />. A script that builds a POST by hand has to use the right one. -
jakarta.faces.ViewStatecarries the identifier (server-side state saving) or the serialised state (client-side state saving) of the view; without it the postback fails with aViewExpiredException. With server-side state saving its unguessable value can also act as a defence against cross-site request forgery, under the conditions the security page of this section spells out (this page did not test it). It is written once in every form.
The attributes of h:form: prependId (whether the form’s id is part of the client ids of its children, true by
default), enctype (multipart/form-data for file uploads), target, accept, acceptcharset, lang, dir,
style, styleClass, title, rendered, binding, role and the DOM events onsubmit and onreset, besides
those in the common table.
Rules to remember:
-
Forms cannot be nested. Browsers do not support a form inside a form, and Faces gives no way around it. Use sibling forms.
-
A command outside a form renders but does nothing useful. In project stage
DevelopmentMojarra adds an informational message to the page: "The form component needs to have a UIForm in its ancestry. Suggestion: enclose the necessary components within `<h:form>`". MyFaces rendered the button silently. -
h:inputFileneedsenctype="multipart/form-data"on its form. Without it Mojarra rendered a development-stage message ("File upload component requires a form with an enctype of multipart/form-data") and the form stayedapplication/x-www-form-urlencoded. The file upload page of this section covers the rest. -
One huge form versus several small ones. Everything in the submitted form is processed, so a validation failure in an unrelated field of the same form blocks the action. Split independent areas, or use
f:ajax executeto limit the fields (Ajax page).
The Bookshelf Beans Used by the Examples
All the examples on this page use two small beans of the Bookshelf. ReviewBean holds the form being edited, so it is
view scoped (the state lives while the user stays on the view and is serialisable); CatalogueBean serves read-only
data to the selection components. The enum and the record are the domain.
package org.example.bookshelf.model;
public enum Genre {
FICTION("Fiction"), SCIENCE("Science"), HISTORY("History");
private final String label;
Genre(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
package org.example.bookshelf.model;
import java.io.Serializable;
public record Book(long id, String title, String author, Genre genre) implements Serializable {
}
package org.example.bookshelf.web;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.model.SelectItem;
import jakarta.faces.model.SelectItemGroup;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.model.Genre;
@Named
@ApplicationScoped
public class CatalogueBean {
private final List<Book> books = List.of(
new Book(1, "Dune", "Frank Herbert", Genre.FICTION),
new Book(2, "Cosmos", "Carl Sagan", Genre.SCIENCE),
new Book(3, "SPQR", "Mary Beard", Genre.HISTORY),
new Book(4, "Solaris", "Stanislaw Lem", Genre.FICTION));
public List<Book> getBooks() {
return books;
}
public Genre[] getGenres() {
return Genre.values();
}
public List<SelectItem> getFormats() {
return List.of(new SelectItem("PAPERBACK", "Paperback"),
new SelectItem("HARDCOVER", "Hardcover"),
new SelectItem("EBOOK", "E-book", "Digital edition", true));
}
public record GenreGroup(String label, List<Book> books) {
}
public List<GenreGroup> getGenreGroups() {
return java.util.Arrays.stream(Genre.values())
.map(g -> new GenreGroup(g.getLabel(), books.stream().filter(b -> b.genre() == g).toList()))
.toList();
}
public SelectItemGroup[] getGroupedBooks() {
List<SelectItemGroup> groups = new ArrayList<>();
for (Genre g : Genre.values()) {
List<SelectItem> items = new ArrayList<>();
for (Book b : books) {
if (b.genre() == g) {
items.add(new SelectItem(b.id(), b.title()));
}
}
groups.add(new SelectItemGroup(g.getLabel(), "", false, items.toArray(new SelectItem[0])));
}
return groups.toArray(new SelectItemGroup[0]);
}
public Book find(long id) {
return books.stream().filter(b -> b.id() == id).findFirst().orElse(null);
}
}
package org.example.bookshelf.web;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.model.Genre;
@Named
@ViewScoped
public class ReviewBean implements Serializable {
private String reviewer;
private String email;
private String password;
private String comment;
private Integer rating;
private boolean recommended;
private String format;
private Genre genre;
private Long bookId;
private Book book;
private List<String> tags = new ArrayList<>();
private List<Genre> favouriteGenres = new ArrayList<>();
private String note = "<b>bold</b> & more";
public String submit() {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Review saved", "Thanks, " + reviewer));
return null;
}
public String getReviewer() { return reviewer; }
public void setReviewer(String reviewer) { this.reviewer = reviewer; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
public Integer getRating() { return rating; }
public void setRating(Integer rating) { this.rating = rating; }
public boolean isRecommended() { return recommended; }
public void setRecommended(boolean recommended) { this.recommended = recommended; }
public String getFormat() { return format; }
public void setFormat(String format) { this.format = format; }
public Genre getGenre() { return genre; }
public void setGenre(Genre genre) { this.genre = genre; }
public Long getBookId() { return bookId; }
public void setBookId(Long bookId) { this.bookId = bookId; }
public Book getBook() { return book; }
public void setBook(Book book) { this.book = book; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public List<Genre> getFavouriteGenres() { return favouriteGenres; }
public void setFavouriteGenres(List<Genre> favouriteGenres) { this.favouriteGenres = favouriteGenres; }
public String getNote() { return note; }
public void setNote(String note) { this.note = note; }
}
(The page on scopes and the page on backing beans and CDI explain why @ViewScoped is the right scope for a
form bean. For some of the checks below the scratch run used a submit that echoed the converted values in the message
detail, so that the values that reached the bean could be read.)
Text Input Components
The four text inputs map to the HTML form controls one to one. They are all UIInput components, so they all share
value, converter, required, validator, immediate, valueChangeListener and the message attributes.
-
h:inputTextrenders<input type="text">. Attributes:maxlength,size,readonly,disabled,alt,autocomplete,accesskeyandtabindex, and since 4.0type. Thetypeattribute (defaulttext) lets the one tag render an HTML5 input such asemail,url,number,dateortelwithout pass-through attributes; the VDL adds that for a type that already has a more specific component (hidden,password,checkbox,radio,file,submit,image,reset,button), the renderer warns in development stage and the more specific component is preferred. The HTML5 page covers the topic. -
h:inputSecretrenders<input type="password">. By default it never writes its value back to the page: the field came back withvalue=""after a postback that had submitted a password.redisplay="true"changes that and should not be used for a real password, since the value would be in the HTML. -
h:inputTextarearenders<textarea>;rowsandcolssize it. -
h:inputHiddenrenders<input type="hidden">. The value is written to the page and the browser sends it back, so anyone can change it. Never treat a hidden field as trusted: it is for state that can be re-checked on the server. -
h:inputFilerenders<input type="file">and submits ajakarta.servlet.http.Part.multiple(to submit aCollectionof parts) andacceptwere added in 4.0. The file upload page of this section covers it.
The Bookshelf review form uses the first three, plus a label and a message beside each:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>Write a review</title>
</h:head>
<h:body>
<h:form id="review">
<h:messages globalOnly="true" showDetail="true" infoClass="ok" errorClass="err"/>
<h:panelGrid columns="3">
<h:outputLabel for="reviewer" value="Name"/>
<h:inputText id="reviewer" value="#{reviewBean.reviewer}"
required="true" requiredMessage="Please enter your name"
maxlength="40" size="30" autocomplete="name"/>
<h:message for="reviewer" styleClass="err"/>
<h:outputLabel for="email" value="E-mail"/>
<h:inputText id="email" type="email" value="#{reviewBean.email}"/>
<h:message for="email" styleClass="err"/>
<h:outputLabel for="password" value="Password"/>
<h:inputSecret id="password" value="#{reviewBean.password}" redisplay="false"/>
<h:message for="password" styleClass="err"/>
<h:outputLabel for="comment" value="Comment"/>
<h:inputTextarea id="comment" value="#{reviewBean.comment}" rows="4" cols="40"/>
<h:message for="comment" styleClass="err"/>
</h:panelGrid>
<h:inputHidden id="rating" value="#{reviewBean.rating}"/>
<h:commandButton id="save" value="Save" action="#{reviewBean.submit}"/>
</h:form>
</h:body>
</html>
Mojarra rendered the body of the form as follows (the <table> is the h:panelGrid, abbreviated):
<table>
<tbody>
<tr>
<td><label for="review:reviewer">Name</label></td>
<td><input id="review:reviewer" type="text" name="review:reviewer" value="" maxlength="40" size="30" /></td>
<td></td>
</tr>
<tr>
<td><label for="review:email">E-mail</label></td>
<td><input id="review:email" type="email" name="review:email" value="" /></td>
<td></td>
</tr>
<tr>
<td><label for="review:password">Password</label></td>
<td><input id="review:password" type="password" name="review:password" value="" /></td>
<td></td>
</tr>
<tr>
<td><label for="review:comment">Comment</label></td>
<td><textarea id="review:comment" name="review:comment" cols="40" rows="4"></textarea></td>
<td></td>
</tr>
</tbody>
</table>
<input id="review:rating" type="hidden" name="review:rating" value="" />
<input id="review:save" type="submit" name="review:save" value="Save" />
Points the output makes visible:
-
The
forof each label became the client id of its input (review:reviewer), resolved from the plain id the page used. That is what makes clicking the label focus the field, and what screen readers use. -
autocomplete="name"did not appear on the first input. -
The empty
h:messagecells are<td></td>: with no message to show and noid,h:messagewrote nothing at all.
What happens on a postback:
-
Apply Request Values. Each input’s renderer reads
review:reviewerand the others from the request. An input whose parameter is absent from the request is left alone (the run posted a form without the field andrequireddid not fire); one that arrives empty stores"". -
Process Validations. An empty
reviewerfailsrequiredand the page redisplays with the message "Please enter your name". WithoutrequiredMessagethe text is the standard "Validation Error: Value is required.", prefixed by thelabelor, when there is no label, by the client id. -
Update Model Values. Valid values are written to the bean. The run posted a name and an empty comment and the bean received
comment='', an empty string, notnull. Setting the context parameterjakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULLtotrueinweb.xmlmakes the same post leave the propertynull(confirmed on Mojarra). According to the specification text, the standard converters already returnnullfor an empty string (read, not run here). -
Invoke Application.
saverunssubmit().
Selection Components
Selection components let the user pick from a list of options. There are seven tags, and they differ in whether they accept one value or many, and in how the options are drawn.
| Tag | Selects | Bean property type | Looks like |
|---|---|---|---|
|
one yes/no |
|
a single checkbox |
|
one of many |
the type of the item values |
a drop-down |
|
one of many |
the type of the item values |
a list box ( |
|
one of many |
the type of the item values |
radio buttons |
|
many |
array or |
a multi-select drop-down |
|
many |
array or |
a multi-select list box ( |
|
many |
array or |
a group of checkboxes |
The options come from child tags, f:selectItem for one and f:selectItems for a collection, both from the core tag
library.
Defining the options
<h:selectOneMenu id="format" value="#{reviewBean.format}">
<f:selectItem itemLabel="-- choose --" itemValue="" noSelectionOption="true"/>
<f:selectItems value="#{catalogueBean.formats}"/>
</h:selectOneMenu>
f:selectItem takes itemValue (what is submitted and converted), itemLabel (what the user sees), itemDescription,
itemDisabled, itemEscaped (default true) and noSelectionOption. f:selectItems takes a value that is an
array, a Collection or a Map:
-
a collection or array of
jakarta.faces.model.SelectItemobjects (the form used forgetFormats()above), or ofSelectItemGroupobjects (see Grouped options: f:selectItemGroup and f:selectItemGroups); -
a collection or array of any objects, in which case
var,itemValueanditemLabelsay how to read each one:<f:selectItems value="#{catalogueBean.books}" var="b" itemValue="#{b.id}" itemLabel="#{b.title} (#{b.author})"/>itemDisabled,itemDescriptionanditemLabelEscapedtake expressions onvartoo, andnoSelectionValuemarks the element that stands for "no selection"; -
a
Map, whose keys are the labels and whose values are the item values. ALinkedHashMapkeeps the order; the run renderedPaperbackandHardcoveras labels over the valuesPAPERBACKandHARDCOVER.
An enum needs no converter: the run submitted FICTION and the property of type Genre received Genre.FICTION,
because Faces applies the enum converter implicitly. Long and the other wrapper types also convert implicitly. Any
other class needs a converter, as shown in Selecting objects, and the "Value is not valid" error.
The itemDisabled item is drawn but cannot be chosen: new SelectItem("EBOOK", "E-book", "Digital edition", true)
rendered <option value="EBOOK" disabled="disabled">E-book</option>. The description was not written to the HTML.
The no-selection option
A menu shows its first option when the value is empty, so a prompt such as "-- choose --" has to be an item with an
empty value. Marking it noSelectionOption="true" (on f:selectItem) or noSelectionValue (on f:selectItems) tells
Faces the item stands for "nothing", and with required="true" a user who leaves it selected fails validation. The run
confirmed it: a required menu left on its prompt came back with "Edition: Validation Error: Value is required." (the
text before the colon is the label).
<h:selectOneMenu id="edition" label="Edition" required="true" value="#{reviewBean.format}">
<f:selectItem itemLabel="-- choose --" itemValue="" noSelectionOption="true"/>
<f:selectItems value="#{catalogueBean.formats}"/>
</h:selectOneMenu>
hideNoSelectionOption="true" removes the prompt once a value is selected. On Mojarra, after a post with
HARDCOVER, the menu came back with the three formats and without the "-- choose --" option. This was not checked on
MyFaces.
Rendering
The same options render differently per tag. All seven in one form, as run:
<h:form id="pick">
<h:selectBooleanCheckbox id="recommended" value="#{reviewBean.recommended}"/>
<h:outputLabel for="recommended" value="I recommend this book"/>
<h:selectOneRadio id="genre" value="#{reviewBean.genre}" layout="pageDirection">
<f:selectItems value="#{catalogueBean.genres}" var="g" itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectOneRadio>
<h:selectOneListbox id="book" size="4" value="#{reviewBean.bookId}">
<f:selectItems value="#{catalogueBean.books}" var="b" itemValue="#{b.id}"
itemLabel="#{b.title} (#{b.author})"/>
</h:selectOneListbox>
<h:selectManyCheckbox id="tags" value="#{reviewBean.tags}" layout="lineDirection">
<f:selectItem itemValue="classic" itemLabel="Classic"/>
<f:selectItem itemValue="short" itemLabel="Short read"/>
</h:selectManyCheckbox>
<h:selectManyListbox id="favs" size="3" value="#{reviewBean.favouriteGenres}">
<f:selectItems value="#{catalogueBean.genres}" var="g" itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectManyListbox>
<h:selectManyMenu id="favmenu" value="#{reviewBean.favouriteGenres}">
<f:selectItems value="#{catalogueBean.genres}" var="g" itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectManyMenu>
</h:form>
<input id="pick:recommended" type="checkbox" name="pick:recommended" />
<label for="pick:recommended">I recommend this book</label>
<table border="0" id="pick:genre">
<tr><td>
<input type="radio" name="pick:genre" id="pick:genre:0" value="FICTION" />
<label for="pick:genre:0"> Fiction</label></td></tr>
<tr><td>
<input type="radio" name="pick:genre" id="pick:genre:1" value="SCIENCE" />
<label for="pick:genre:1"> Science</label></td></tr>
...
</table>
<select id="pick:book" name="pick:book" size="4">
<option value="1">Dune (Frank Herbert)</option>
<option value="2">Cosmos (Carl Sagan)</option>
...
</select>
<select id="pick:favs" name="pick:favs" multiple="multiple" size="3">
<option value="FICTION">Fiction</option>
...
</select>
<select id="pick:favmenu" name="pick:favmenu" multiple="multiple" size="1">
...
</select>
(The listing is re-indented for reading. The h:selectManyCheckbox markup has the shape of the radio table, with
<input type="checkbox"> items on a single row for lineDirection.)
How the pieces are named:
-
The
<select>and the<table>carry the component’s client id; each radio and checkbox gets that id with:0,:1, … appended, which is also what each<label for>points at. -
h:selectBooleanCheckboxhas no label of its own: pair it with anh:outputLabel. When the checkbox is unchecked the browser sends nothing, and the renderer treats a missing parameter asfalse, so the property is correctly reset (the specification defines this in the decode behaviour). -
h:selectManyMenuis a<select multiple>that Mojarra rendered withsize="1"; unlike the list box it has nosizeattribute, so the browser decides how to draw it. -
h:selectManyCheckboxandh:selectManyListboxtake acollectionTypenaming theCollectionclass to create when the property is not an array or already initialised (collectionType="java.util.TreeSet"produced aTreeSet).
Layout of radio buttons and checkboxes
h:selectOneRadio and h:selectManyCheckbox accept layout:
layout |
Markup |
|---|---|
|
one |
|
one |
|
a |
<h:selectOneRadio id="listradio" value="#{reviewBean.genre}" layout="list">
<f:selectItems value="#{catalogueBean.genres}" var="g" itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectOneRadio>
<h:selectManyCheckbox id="listcheck" value="#{reviewBean.favouriteGenres}" layout="list" styleClass="opts">
<f:selectItems value="#{catalogueBean.genres}" var="g" itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectManyCheckbox>
<ul id="pick:listradio">
<li>
<input type="radio" name="pick:listradio" id="pick:listradio:0" value="FICTION" /><label for="pick:listradio:0"> Fiction</label></li>
<li>
...
</ul>
<ul id="pick:listcheck" class="opts">
<li>
<input name="pick:listcheck" id="pick:listcheck:0" value="FICTION" type="checkbox" /><label for="pick:listcheck:0" class=""> Fiction</label></li>
...
</ul>
The styleClass moved to the <ul>, and list worked the same on MyFaces. The VDL also lists enabledClass
and disabledClass for the labels of both tags, and selectedClass and unselectedClass for the checkboxes.
Radio buttons that must sit in different places of the page can use the group attribute of h:selectOneRadio
(Faces 2.3). Each tag then renders only the radios of its own items, and all of them share the name of the group:
<h:selectOneRadio id="a" group="genre" value="#{reviewBean.genre}">
<f:selectItem itemValue="FICTION" itemLabel="Fiction"/>
</h:selectOneRadio>
<h:selectOneRadio id="b" group="genre" value="#{reviewBean.genre}">
<f:selectItem itemValue="SCIENCE" itemLabel="Science"/>
</h:selectOneRadio>
Mojarra rendered <input type="radio" name="r:genre" id="r:a" value="r:a:FICTION" /> and a second radio with
id="r:b" and value="r:b:SCIENCE", and a post of r:b:SCIENCE set the property to SCIENCE. The group attribute is described in the rendering text
of the VDL for h:selectOneRadio and in the tutorial, although it is missing from that tag’s attribute table. On
MyFaces 4.1.4 the same markup rendered correctly for a String property, but with an enum-typed property the page
failed with "r:a: 'FICTION' must be convertible to an enum."; this is a MyFaces behaviour to check before relying on
group with enums.
Grouped options: f:selectItemGroup and f:selectItemGroups
Menus and list boxes can draw <optgroup> headings. There are two ways to describe the groups.
From Java, return SelectItemGroup objects (a label plus its SelectItem children) in the collection given to
f:selectItems; getGroupedBooks() above does that:
<h:selectOneMenu id="grouped" value="#{reviewBean.bookId}">
<f:selectItems value="#{catalogueBean.groupedBooks}"/>
</h:selectOneMenu>
From the view, f:selectItemGroup (a single group, 4.0) and f:selectItemGroups (a group for every element of a
collection, 4.0) nest f:selectItems children. f:selectItemGroups takes value (array or Iterable), var
(the current group, visible to the nested tags) and itemLabel and itemDisabled. The nested f:selectItems must read
the items of that group from var; pointing it at the whole book list would repeat every book in every group (the run
printed four books under each heading until value was changed to g.books):
<h:selectOneMenu id="grouped2" value="#{reviewBean.bookId}">
<f:selectItemGroups value="#{catalogueBean.genreGroups}" var="g" itemLabel="#{g.label}">
<f:selectItems value="#{g.books}" var="b" itemValue="#{b.id}" itemLabel="#{b.title}"/>
</f:selectItemGroups>
</h:selectOneMenu>
Both forms rendered the same HTML:
<select id="pick:grouped2" name="pick:grouped2" size="1">
<optgroup label="Fiction">
<option value="1">Dune</option>
<option value="4">Solaris</option>
</optgroup>
<optgroup label="Science">
<option value="2">Cosmos</option>
</optgroup>
<optgroup label="History">
<option value="3">SPQR</option>
</optgroup>
</select>
Radio and checkbox renderers draw a group as a nested root element (a nested table or list) instead.
Selecting objects, and the "Value is not valid" error
Faces compares what the browser sent with the options that were rendered. Two errors follow from that.
The submitted value must be one of the options. A value that is not in the list (a forged request, or a list that
changed between render and postback) fails with "pick:book: Validation Error: Value is not valid" (the run posted a
book id that was not offered). Rebuild the list identically on the postback: a @RequestScoped or @ViewScoped bean
that recomputes the options must produce the same set, and a list that depends on request state must be rebuilt from
state that survives (view scope).
Item values that are not simple types need a converter. The Bookshelf menu below uses the Book objects themselves as
item values, with no converter:
<h:selectOneMenu id="objmenu" value="#{reviewBean.book}">
<f:selectItems value="#{catalogueBean.books}" var="b" itemValue="#{b}" itemLabel="#{b.title}"/>
</h:selectOneMenu>
The renderer used toString() for the option values (Book[id=1, title=Dune, ...]) and the postback failed with
"Conversion Error setting value 'Book[id=1, …]' for 'null Converter'.". A converter that maps the book to its id and
back repairs it. A managed converter, registered for the class and able to inject the catalogue, is the shortest form
(converters page):
@FacesConverter(forClass = Book.class, managed = true)
@ApplicationScoped
public class BookConverter implements Converter<Book> {
@Inject
private CatalogueBean catalogue;
@Override
public Book getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isBlank()) {
return null;
}
try {
return catalogue.find(Long.parseLong(value));
} catch (NumberFormatException e) {
throw new ConverterException(e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Book value) {
return value == null ? "" : String.valueOf(value.id());
}
}
With it the same menu rendered <option value="1">Dune</option> and a post of 3 set the property to the SPQR
book. The alternative that needs no converter is to submit the id (itemValue="#{b.id}", a Long) and look the book
up in the action, as the list box above does.
Labels are escaped. itemLabel and itemLabelEscaped (and itemEscaped on f:selectItem) default to true. Setting
them to false made the label <b>bold</b> render as real bold markup in the run, which is only safe for labels
written by the page author and never for text a user can influence.
Output Components
h:outputText
Writes the value as text. The text is escaped by default: with note = "<b>bold</b> & more",
<h:outputText id="n1" value="#{reviewBean.note}"/>
<h:outputText id="n2" value="#{reviewBean.note}" escape="false"/>
<h:outputText id="n3" value="#{reviewBean.note}" styleClass="note" title="a note"/>
<span id="n1"><b>bold</b> & more</span>
<span id="n2"><b>bold</b> & more</span>
<span id="n3" class="note" title="a note"><b>bold</b> & more</span>
The <span> exists only when the tag has an id, a style or another attribute that needs an element; without any
of them the renderer writes just the text (the run printed no id for <h:outputText value="no id"/>). escape="false" writes the value as markup. It must never be used for
text that comes from a user, a database field they can edit or a request parameter, because that is
cross-site scripting; escape the value or render it with the default. The same goes for h:outputLabel escape and for
itemLabelEscaped. Expressions written in template text are escaped the same way (see
the escaping section of the Facelets page).
h:outputText also takes converter, so <h:outputText value="#{book.published}"><f:convertDateTime/></h:outputText>
formats a date (converters page).
h:outputFormat
Builds a message with java.text.MessageFormat, with parameters supplied by f:param children in order:
<h:outputFormat id="fmt" value="{0} has {1} reviews">
<f:param value="Dune"/>
<f:param value="42"/>
</h:outputFormat>
which produced <span id="fmt">Dune has 42 reviews</span>. The value is usually a resource bundle message such as
#{msg['book.reviews']}, so the sentence is translated while the parameters stay in the view (messages and
internationalisation page).
h:outputLabel
Renders <label for="..."> and is the accessible way to name an input. for takes the id of the input as written in
the page and Faces resolves it to the client id, as the review form showed. The text comes from value or from
child content, and escape defaults to true. Always give inputs a label: the label increases the click target, and
assistive technology reads it; see Accessibility and
Forms: Accessibility & Validation for the rules.
If for names no component, the behaviour differs: in the scratch run an h:outputLabel outside any form rendered a
bare <label> on Mojarra and <label for=""> on MyFaces. Check the rendered HTML when labels do not focus
their input.
h:outputLink
Writes <a href="..."> from value, with f:param children added as encoded query parameters. It is a link only: no
action, no Faces navigation and no postback, so it suits external URLs.
<h:outputLink id="docs" value="https://jakarta.ee/specifications/faces/4.1/">Spec</h:outputLink>
<h:outputLink value="/bookshelf/inputs.xhtml"><f:param name="a" value="b c"/>Link text</h:outputLink>
The second rendered <a href="/bookshelf/inputs.xhtml?a=b+c">Link text</a>: the value b c was URL-encoded. For links
inside the application that should follow navigation rules and view parameters, use h:link (Buttons and Links).
h:graphicImage
Renders <img>. There are two ways to give the source:
-
nameandlibraryrefer to a resource: a file underresources/(hereresources/img/cover.png), served through the Faces resource handler with caching and versioning.<h:graphicImage name="cover.png" library="img"/>renderedsrc="/bookshelf/jakarta.faces.resource/cover.png.xhtml?ln=img"on both implementations. -
url(or its aliasvalue) is a context-relative URL.<h:graphicImage url="/images/cover.png"/>renderedsrc="/bookshelf/images/cover.png": a path starting with/is prefixed with the context path.
Always give alt (empty for a purely decorative image). width, height, longdesc, usemap and ismap pass
through. The resource handling page of this section explains libraries, versions and locales.
h:outputScript, h:outputStylesheet, h:head and h:body
h:outputStylesheet name="app.css" library="css" rendered <link rel="stylesheet" href="...?ln=css" /> inside the
page head; h:outputScript renders a <script src> and accepts target="head" or target="body". A script that is
written twice with the same name and library was rendered once. These two tags need h:head and h:body rather than
the plain HTML elements, because the renderers of h:head and h:body are where resources targeted at the head and
the body are written (the Facelets page explains it in
the h:head and h:body section). Faces also needs its
faces.js library for Ajax and for h:commandLink; Mojarra wrote the <script> for it inline, just before the first
command link of the form.
h:doctype renders a document type declaration at the position it is written, for views that do not use an
HTML5 <!DOCTYPE html>. <h:doctype rootElement="html" public="-//W3C//DTD HTML 4.01//EN"
system="http://www.w3.org/TR/html4/strict.dtd"/> rendered the matching declaration in the run; rootElement is
required (without it Mojarra wrote <!DOCTYPE null> and MyFaces <!DOCTYPE >). Ordinary Facelets pages simply start
with <!DOCTYPE html>, as in the examples above.
Since Faces 4.1 the VDL lists an id attribute on h:head and h:body (the component identifier, unique within the closest
naming container). The documentation adds that the attribute is rendered only when the current doctype is an HTML5
doctype, so <h:body id="page"> yields <body id="page"> on a <!DOCTYPE html> page and no id on an older doctype.
This rendering rule is quoted from the VDL; it was not run on either implementation.
Buttons and Links
There are six ways to trigger something from a Faces page. They differ in whether they submit the form, whether they use POST or GET, and what the target is.
| Tag | Method | Needs a form | Use it for |
|---|---|---|---|
|
POST (postback) |
yes |
submit, reset or plain button; runs an action |
|
POST (postback, via JavaScript) |
yes |
an action that looks like a link, such as "Delete" in a table row |
|
GET (JavaScript sets |
no |
navigation to another view without side effects |
|
GET (plain |
no |
the same, as a real link that can be bookmarked and opened in a new tab |
|
GET |
no |
a URL that is not a Faces view |
|
Ajax POST |
yes |
a JavaScript function that calls the server |
<h:form id="nav">
<h:commandButton id="save" value="Save" action="#{reviewBean.submit}"/>
<h:commandButton id="reset" type="reset" value="Reset"/>
<h:commandLink id="more" value="Save and stay" action="#{reviewBean.submit}">
<f:param name="from" value="link"/>
</h:commandLink>
<h:button id="home" value="Home" outcome="inputs"/>
<h:link id="sel" value="Selects" outcome="selects" fragment="top">
<f:param name="q" value="dune"/>
</h:link>
<h:outputLink id="docs" value="https://jakarta.ee/specifications/faces/4.1/">Spec</h:outputLink>
</h:form>
Mojarra’s output:
<input id="nav:save" type="submit" name="nav:save" value="Save" />
<input id="nav:reset" type="reset" name="nav:reset" value="Reset" />
<script src="/bookshelf/jakarta.faces.resource/faces.js.xhtml?ln=jakarta.faces&v=4.1.17&stage=Development"></script>
<a id="nav:more" href="#"
onclick="mojarra.cljs(document.getElementById('nav'),{'nav:more':'nav:more','from':'link'},'');event.preventDefault()">Save and stay</a>
<input id="nav:home" type="button"
onclick="window.location.href='/bookshelf/inputs.xhtml'; return false;" value="Home">
<a id="nav:sel" name="nav:sel" href="/bookshelf/selects.xhtml?q=dune#top">Selects</a>
<a id="nav:docs" name="nav:docs" href="https://jakarta.ee/specifications/faces/4.1/">Spec</a>
Source: Jakarta Faces 4.1 VDL: h:commandButton, Jakarta Faces 4.1 VDL: h:commandLink, Jakarta Faces 4.1 VDL: h:button, Jakarta Faces 4.1 VDL: h:link and Jakarta Faces 4.1 VDL: h:outputLink
h:commandButton and h:commandLink
Both fire an ActionEvent when activated. action is either a method expression whose result is the navigation
outcome (#{reviewBean.submit} returned null, so the same view was redisplayed) or a literal outcome string.
actionListener runs before the action and cannot change the navigation; immediate="true" moves both to the Apply
Request Values phase and skips conversion and validation of the other inputs, which is right for a "Cancel" button and
wrong almost everywhere else (lifecycle page).
h:commandButton takes type (submit by default, button, reset) and image. With image the element is
<input type="image"> and the value is not shown: image="/images/cover.png" rendered type="image" and
src="/bookshelf/images/cover.png". type="reset" clears the browser form without contacting the server, as does a
type="button" with its own onclick; neither triggers an action. The specification says a reset is ignored on
decode.
h:commandLink has no HTML equivalent of its own: it renders an anchor whose onclick submits the surrounding form
with JavaScript that Faces provides (mojarra.cljs on Mojarra, myfaces.oam.submitForm on MyFaces) and adds
faces.js. It therefore requires JavaScript and does not work in a new tab, and f:param children become extra
request parameters of the post (from=link above). Prefer a button for destructive actions and a link for navigation.
h:button and h:link
These two are the outcome-target components. They have no action; they use outcome and the navigation rules to
compute a URL at render time, and the user’s click is a plain GET. Attributes: outcome, includeViewParams (add the
current view parameters to the URL), fragment (the #anchor), f:param children (query parameters), disabled,
disableClientWindow, and for h:button an image, for h:link the anchor attributes such as rel and target. The page on navigation covers outcomes
and the page on view parameters covers includeViewParams.
Because the URL exists in the HTML, h:link supports "open in new tab", bookmarking and crawling, and it needs no
postback or view state. When the outcome cannot be resolved Faces disables the control rather than render a broken
URL: <h:link outcome="nonexistent"/> became a <span> with the text "gone: This link is disabled because a
navigation case could not be matched." and an h:button was rendered disabled, with the same message, and a
development-stage message "Unable to find matching navigation case from view ID '/more.xhtml' for outcome
'nonexistent'" was added. (These messages appear in Development stage only.)
A GET must not change state. A link such as ?delete=42 that deletes on request is exposed to prefetching, crawlers and
cross-site request forgery; changes belong to a POST command component with view state.
h:commandScript
h:commandScript (2.3) declares a JavaScript function that, when called, sends an Ajax request (execute, render,
resetValues, onevent, onerror and action work as on f:ajax). name is required; autorun="true" calls the
function when the page loads.
<h:form id="up">
<h:commandScript id="cs" name="refreshFeed" execute="@form" render="out" action="#{reviewBean.submit}"/>
<h:outputText id="out" value="x"/>
</h:form>
The renderer wrote <span id="up:cs"><script>var refreshFeed=function(o){...mojarra.ab('up:cs',null,'action','@form','up:out',{'params':o})}</script></span>:
the identifier render="out" was resolved to the client id up:out, and calling refreshFeed({reviewer: 'x'}) from any
script passes the object properties as request parameters. This is the tool to trigger Faces from non-Faces JavaScript
such as a timer, a WebSocket handler or a third-party widget; the Ajax page of this section covers the details, and WebSocket push shows the server-initiated case.
Panels: h:panelGrid and h:panelGroup
h:panelGrid lays its children out in an HTML table with columns cells per row. The header, footer and caption
facets become <thead>, <tfoot> and <caption>; rowClasses and columnClasses (comma-separated lists, cycled)
style rows and cells; bodyrows starts a new <tbody> at the given row indices.
<h:panelGrid id="grid" columns="2" border="1" columnClasses="k,v" rowClasses="odd,even">
<f:facet name="header">Details</f:facet>
<h:outputText value="Title"/><h:outputText value="Dune"/>
<h:outputText value="Author"/><h:outputText value="Frank Herbert"/>
<f:facet name="footer">end</f:facet>
</h:panelGrid>
<table id="grid" border="1">
<thead>
<tr><th colspan="2" scope="colgroup">Details</th></tr>
</thead>
<tfoot>
<tr><td colspan="2">end</td></tr>
</tfoot>
<tbody>
<tr class="odd">
<td class="k">Title</td>
<td class="v">Dune</td>
</tr>
<tr class="even">
<td class="k">Author</td>
<td class="v">Frank Herbert</td>
</tr>
</tbody>
</table>
The footer comes before the body in the markup, as the HTML table model requires. Two cautions:
-
h:panelGridis for tabular arrangements, such as a label/input form. Whole-page layout belongs to CSS (grid or flexbox); apanelGridofpanelGrid`s produces nested tables that are hard to style and to read with a screen reader. `h:panelGridcannot span cells: every child occupies exactly one cell, so a component that needs a wider cell must be wrapped, and several components that must share one cell go in anh:panelGroup. -
Count the children. A
columns="2"grid fills two cells per row from the children that are rendered, so a child withrendered="false"does not leave a gap: with children A, B (not rendered), C and D the run put A and C on the first row and D alone on the second, which shifts every following cell. Anh:messagethat has nothing to show still occupies its cell (<td></td>), which is why the review form keeps its columns aligned. MyFaces padded the short last row with an empty<td>; Mojarra did not.
h:panelGroup groups components so that they count as one child, and optionally renders a container. With
layout="block" it is a <div>, otherwise a <span>, and in both cases only when it has an id, a style or a
class. In the run <h:panelGroup id="blk" layout="block" styleClass="box">inside</h:panelGroup> gave
<div id="blk" class="box">inside</div>, a group with an id and no layout gave <span id="inl">inline</span>, and
a group with neither an id nor a style produced no element, only its children. It is also the standard way to give an
Ajax render target to a block of components that may or may not be rendered: if the container is conditional, there
is no element in the page to update.
h:dataTable and h:column produce a table over a collection and are covered on the data tables and iteration page of
this section.
Messages: h:message and h:messages
Faces collects messages (jakarta.faces.application.FacesMessage) during the request, from converters, validators,
Bean Validation, the framework and from your code. Two components display them.
h:message |
h:messages |
|
|---|---|---|
Shows |
the messages for one component ( |
all the messages, or those of one component ( |
Default text shown |
the detail ( |
the summary ( |
Layout |
one message |
|
Both take errorClass, warnClass, infoClass, fatalClass and the matching *Style, tooltip (show the detail as a
title), and styleClass, style, title, lang and dir; h:message also takes role. A message added with a client id is
"component-associated"; one added with null is global. The review bean’s action added a global message and the form
showed it with <h:messages globalOnly="true" showDetail="true"/>:
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Review saved", "Thanks, " + reviewer));
<ul><li class="ok"> Review saved Thanks, Ann</li></ul>
(The list item had both the summary and the detail because the tag asked for both.) Validation and conversion failures
are associated with their component, so a per-field h:message shows them. The text of a failure starts with the
component’s label or, when it has none, its client id:
<span id="f:m" class="err" title="f:name: Validation Error: Value is required."> f:name: Validation Error: Value is required.</span>
Give every input a label attribute to get "Edition: Validation Error: Value is required." rather than
"pick:reqmenu: …". Replace the standard texts with
requiredMessage, converterMessage and validatorMessage on the input, or per language with a message bundle
(messages and internationalisation page).
Two behaviours help with Ajax:
-
A message component with no messages still renders its element when it has an
id:h:messages id="msgs"wrote<div id="f:msgs"></div>(or a<table>, withlayout="table") andh:message id="m"wrote<span id="f:m"></span>. That empty container is what anf:ajax render="msgs"replaces, and it is why a message component needs anidto take part in partial rendering. A message tag with no id and no content wrote nothing. -
In project stage
Development, a message that noh:messageorh:messagesdisplayed is not lost: Mojarra added a<ul id="jakarta_faces_developmentstage_messages">to the end of the page, MyFaces a<div>with the same id. It is how you find out that a validation error was raised for a component that has no message tag.
Rendered HTML and Client IDs
The id you write is only the last segment of the id that reaches the browser. The client id is built from the ids of
all the naming containers above the component, joined with the separator character, which is : by default.
h:form, h:dataTable, ui:repeat and composite components are naming containers (the architecture page explains how
client ids are built). The scratch run printed these cases (Mojarra):
<h:form id="search">
<h:inputText id="q" value="#{reviewBean.reviewer}" onfocus="console.log('#{component.clientId}')"/>
<h:inputText value="#{reviewBean.email}"/>
<h:panelGroup id="g"><h:inputText id="inner"/></h:panelGroup>
</h:form>
<h:form id="noprefix" prependId="false">
<h:inputText id="q2"/>
</h:form>
<h:form>
<h:inputText id="q3"/>
</h:form>
<h:form id="rep">
<ui:repeat value="#{catalogueBean.books}" var="b" varStatus="s">
<h:inputText id="t" value="#{b.title}"/>
</ui:repeat>
</h:form>
<h:form id="dt">
<h:dataTable id="tbl" value="#{catalogueBean.books}" var="b">
<h:column><h:outputText id="title" value="#{b.title}"/></h:column>
</h:dataTable>
</h:form>
<input id="search:q" type="text" name="search:q" value="" onfocus="console.log('search:q')" />
<input type="text" name="search:j_idt5" value="" />
<span id="search:g"><input id="search:inner" type="text" name="search:inner" value="" /></span>
<input id="q2" type="text" name="q2" value="" />
<form id="j_idt6" ...><input id="j_idt6:q3" type="text" name="j_idt6:q3" value="" />
<input id="rep:j_idt7:0:t" type="text" name="rep:j_idt7:0:t" value="Dune" />
<input id="rep:j_idt7:1:t" ... value="Cosmos" />
<span id="dt:tbl:0:title">Dune</span>
<span id="dt:tbl:1:title">Cosmos</span>
What it shows:
-
A component without an
idgets a generated one,j_idt5. Mojarra used it only as thenameof the input and did not write an HTMLidfor it; MyFaces wroteid="search:j_id_8"too. It is not stable: adding a component earlier in the page renumbers the later ones. In the scratch app the submit button waspick:j_idt21and becamepick:j_idt25after two menus with theirf:selectItemandf:selectItemschildren were added above it, so a test or script that hard-coded the generated id broke. Any component you refer to must have an explicitid. MyFaces generatesj_id_8instead. -
prependId="false"removes the form’s id from its children (q2). The risk is duplicates: two forms with the same child id then collide. Do not use it with several forms on a page. -
Inside a repeating component the client id includes the row index (
dt:tbl:0:title), and aui:repeatadds its own generated id segment (rep:j_idt7:0:t) when it has noid; give it one. -
The
nameattribute of an input is its client id, which is what the decode reads. -
#{component.clientId}is available in the attribute of a tag and gives the id of the component the attribute belongs to, the stable way to hand an id to JavaScript.
The separator is configurable. The context parameter jakarta.faces.SEPARATOR_CHAR set to - made the same page
render search-q and search-g, which avoids escaping in CSS selectors (the colon of a client id must be written
#search\:q in a CSS selector, while document.getElementById('search:q') needs no escaping). The parameter is global, so everything that builds ids by hand or tests that match them
must agree; a component library can break when the separator changes. The default is the safe choice.
Rules of thumb:
-
Reference components by search expression (
@form,@this, absolute ids starting with:, relative ids) fromrenderandexecute, and avoid string-concatenating client ids in Java. The page on search expressions describes them. -
Use CSS classes, not
#idselectors, for styling. -
In tests, give the elements
data-testidpass-through attributes (HTML5 page) or `id`s, and match with a stable selector instead of the generated id.
Where the Components Act in the Request Processing Lifecycle
| Phase | What the standard HTML components do |
|---|---|
Restore View |
On the first request the component tree is built from the Facelets view; on a postback it is rebuilt and the view state is restored. Every tag of this page becomes a component here. |
Apply Request Values |
Every component that is rendered, enabled and not read-only calls |
Process Validations |
The submitted strings are converted, then validated: |
Update Model Values |
Valid values are written to the beans. |
Invoke Application |
Action listeners and actions of |
Render Response |
Renderers encode the HTML; |
Two server-side rules follow from this and were checked on both implementations. A request that sends a value for a
component that is disabled, readonly or has rendered="false" is ignored for that component: the bean kept its
old value when the run posted outer:a=hacked, outer:b=hacked and outer:c=hacked for a disabled, a read-only and a
non-rendered input. This protects the data that the page hides or locks, but only as far as the attribute is not itself
computed from forgeable state; and it is not authorisation: a user who can reach the action can call it, whatever the
page shows. Check permissions in the bean or with Jakarta Security (security page), not with rendered.
See the lifecycle page for the six phases in full,
Apply Request Values and
the immediate attribute.
Mojarra Versus MyFaces
Both implementations pass the Jakarta Faces compatibility tests, so the components behave the same. What differs is markup details and defaults, which matters when you test with strings, write CSS that depends on the structure, or post forms with scripts. Differences seen in the scratch run (Mojarra 4.1.17, MyFaces 4.1.4):
| Area | Mojarra | MyFaces |
|---|---|---|
Hidden field identifying the submitted form |
|
|
Generated ids |
|
|
Whitespace in output |
newlines and tabs inside tables and selects |
compact, mostly single-line markup |
Checkbox |
|
adds |
Labels in |
a space before the label text, |
the entity |
|
|
|
|
|
|
No-selection prompt of a menu whose value is |
the prompt option is rendered, not marked |
the prompt option has |
|
the row has fewer cells |
the missing cell is padded with an empty |
|
|
|
|
|
|
Command outside a form |
development-stage message about the missing |
no message in the run |
|
works |
failed to render in 4.1.4 (String property worked) |
The markup differences are harmless to pages written to the specification: use ids and classes you set yourself, not the generated structure, and never compare rendered HTML in a test. The versions page compares the implementations in general.
Common Pitfalls
-
Inputs or commands outside an
h:form. They render but do not submit. Mojarra’s development-stage message names the cause; MyFaces may say nothing. -
Nested forms. Not supported by HTML; use sibling forms.
-
A required field that is skipped by the button you want. The button validates the whole form. Put the "Cancel" button in its own form, or in the same form with
immediate="true"only after reading the lifecycle page. -
Missing
label. Messages start with a client id such aspick:reqmenu, which means nothing to the user. -
escape="false"anditemLabelEscaped="false"with user text. Cross-site scripting. Escape, or sanitise on the way in and render with the default. -
Select lists that change between request and postback. Fails with "Value is not valid". Build the list from state that does not change within the view, and make
equals()of object values consistent with the converter. -
Objects as item values without a converter. "Conversion Error setting value … for 'null Converter'". Register a converter for the class, or submit the id.
-
Relying on generated ids (
j_idt21). Give an explicitidto anything you refer to. -
Business logic in getters bound to
value. A getter runs several times per request for each component, in both rendering and decoding; the lifecycle page explains why. Compute in an action or@PostConstructand return a field. -
Hidden fields as a security measure. The browser can send anything; recheck server-side.
-
A state-changing
h:linkorh:button. GET must not change state. -
h:commandLinkfor plain navigation. It requires JavaScript, is not crawlable and cannot open in a new tab. Useh:link. -
bindingon a bean that outlives the request. Components are not thread-safe or serialisable in the way scoped beans expect (architecture page). -
Too much in one
h:panelGrid. A table per field row is hard to style; consider CSS grid on adivwithh:panelGroup layout="block"children.
What Changed Between Versions
| Version | Change for the standard HTML tags |
|---|---|
1.x |
The tags were used from JSP pages through tag handlers, with the namespaces |
2.0 |
Facelets becomes the standard view technology. The VDL documentation marks |
2.1 |
The VDL documentation marks |
2.2 |
|
2.3 |
|
3.0 |
The namespaces become |
4.0 |
Namespace URNs |
4.1 |
|
5.0 (milestones) |
Not released. Planned work is listed on the versions page; none of it is assumed here. The specification page lists a |
Source: Jakarta Faces 4.1 specification, Appendix B: Change Log, the change markers in the Jakarta Faces 4.1 VDL documentation and the planned items on Jakarta Faces 5.0 (under development)
Where the Books Are Outdated
The standard component library is the part of the books that has aged best, because the tags are stable. Still:
-
JavaServer Faces 2.0: The Complete Reference (Burns and Schalk), which has a full reference of the standard components and chapters on selection and output components, was written for Faces 2.0. It does not have the
typeattribute ofh:inputText,layout="list",f:selectItemGroups, thegroupattribute ofh:selectOneRadio,multipleandacceptonh:inputFile,h:commandScript, thejakarta.faces.*namespace URNs, or thejakarta.package names (its Java usesjavax.faces). Its tag reference is still a useful guide to the older attributes. -
The books that predate Faces 2.0 (the Mann and the Dudney books) show JSP pages with
<%@ taglib uri="http://java.sun.com/jsf/html" %>, thef:viewwrapper andh:outputTextfor every piece of text. JSP views were removed in 4.0, and on Facelets expressions work in template text. The component model behind the tags is the same, which is the reason to keep those books for background. -
Recipes in the JSF 2.0 Cookbook and similar books that add file upload, rich tables or selection widgets with Tomahawk (
t:), Trinidad or RichFaces components target libraries that no longer exist; with the standard tags, useh:inputFile,layout="list"and CSS, and for richer widgets the PrimeFaces components (PrimeFaces pages, other libraries). -
The PrimeFaces Cookbook shows
p:components that stand in for the standard ones (p:inputText,p:selectOneMenu,p:commandButton); the standard tags remain the base, and PrimeFaces 16 builds on them.
Use the books for the explanation of the component model and the way the tags are meant to be combined, and the VDL documentation and this page for the attribute lists and rendered HTML of Faces 4.1.
Source: JavaServer Faces 2.0: The Complete Reference, McGraw-Hill, publisher page
References
-
Jakarta EE Tutorial: Adding Components to a Page Using HTML Tag Library Tags
-
Jakarta Faces 4.1 specification, 9.5 Standard HTML RenderKit Tag Library
-
Jakarta Faces 4.1 specification, 2.2 Standard Request Processing Lifecycle Phases
-
Jakarta Faces 4.1 VDL documentation: the h, f and ui tag libraries
-
JavaServer Faces 2.0: The Complete Reference, McGraw-Hill (publisher page)