Other Component Libraries: BootsFaces, Tobago, AdminFaces and the Libraries That Ended
|
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) with Eclipse
Mojarra 4.1.17 and Apache MyFaces Core 4.1.4, and Java 17 or later (the examples were compiled with --release 21). The libraries
are BootsFaces 2.0.1, Apache MyFaces Tobago 6.12.2 and AdminFaces' admin-template 2.0, and the ones that ended:
RichFaces 4.5.17.Final, ICEfaces 4.3.0, Tomahawk 1.1.14 and Trinidad 2.2.1. Every version was read from Maven Central and
the project sites on 2026-10-08. The Bookshelf pages below were deployed as exploded WARs on Tomcat 11.0.26 with
Weld 6.0.4.Final (Tomcat and Weld), once with Mojarra and once with MyFaces, and driven with curl and a headless Chrome. Anything that was
only read from documentation or from a library’s source is marked as not run.
The two libraries that dominate Faces projects, PrimeFaces and OmniFaces, have their own pages in this section (the PrimeFaces pages and the OmniFaces page) and are only named here. This page is about everything else: the libraries you meet in an existing code base, the ones a project may still choose, and the ones that no longer exist and have to be replaced.
The Landscape in 2026
A component library is a set of tags, components and renderers on top of the standard ones. Jakarta Faces does not ship tables with a pager, dialogs, menus or a grid layout; libraries fill that gap, and each makes a different bet about the markup, the CSS and the JavaScript it brings. The table is the state of the libraries this page covers, from Maven Central and the projects' own pages.
| Library | Newest release | Status | What it targets |
|---|---|---|---|
BootsFaces |
2.0.1 (2024-09-02) |
The README says the project is not maintained actively and asks for pull requests |
Jakarta EE 10 (Faces 4.0), Java 11; Bootstrap 3.4.1 and jQuery 3.7.1 bundled |
Apache MyFaces Tobago |
6.12.2 (2026-09-27) |
Active: six releases in 2026 on the 6 line, plus maintenance releases of the 5.x line |
Faces 4.0 per the project’s table, Java 17; Bootstrap 5.3.8 bundled; run here on Faces 4.1 |
AdminFaces |
2.0 (2023-05-07) |
The GitHub repository was last pushed to on 2026-04-16, but the newest release is from May 2023 |
Jakarta EE 10; pulls in PrimeFaces 12.0.0 and OmniFaces 4.1 |
RichFaces |
4.5.17.Final (2016-06-20) |
Ended: the project site says end-of-life in June 2016 |
|
ICEfaces |
4.3.0 (2018-08-27, Maven Central) |
Still offered commercially by its vendor; no open-source release after 2018 on Central |
|
MyFaces Tomahawk |
1.1.14 (2012-10-23) |
Listed under Inactive Projects (Maintenance Mode) |
JSF 1.1 to 2.1 |
MyFaces Trinidad |
2.2.1 (2017-07-21) |
Listed under Inactive Projects (Maintenance Mode) |
|
Source: Maven Central: net.bootsfaces:bootsfaces, Apache MyFaces: Tobago, Apache MyFaces: Inactive Projects, AdminFaces admin-template on GitHub and RichFaces project page
Two rules of thumb come out of the table. A library whose newest release is older than the Jakarta rename (Faces 3.0)
cannot be used with Faces 4.1 at all, because the jakarta.faces classes are not the javax.faces classes it
extends. A library that does use the
jakarta.faces.* packages can still break on 4.1, because 4.1 changed an interface (see The ActionSource2 Trap on Faces 4.1); "supports
Jakarta EE 10" on the project page is a starting point, not a guarantee.
How a Library Plugs Into Faces
Everything on this page uses the same mechanisms, which are described in Custom Components and Renderers and Extending Faces:
-
A tag library. The jar contains a Facelets tag library (
META-INF/*.taglib.xml) that maps tag names to component types and renderer types under a namespace URI. BootsFaces 2.0.1 declares 82 tags underhttp://bootsfaces.net/ui(not ajakarta.faces.*URN: the old-style URI is still how it is used), Tobago 6.12.2 declares its tags underhttp://myfaces.apache.org/tobago/component. -
A
faces-config.xmlinMETA-INF. It registers components, renderers, listeners and, in Tobago’s case, a whole render kit. BootsFaces' file registers one system event listener,net.bootsfaces.listeners.AddResourcesListener, onPreRenderViewEvent; it adds the library’s CSS and JavaScript to the page head. Tobago’s file setstobagoas the default render kit id, adds a message bundle, and adds a phase listener (SecretPhaseListener, see Built-In Security). -
Resources under
META-INF/resources, served by theResourceHandler(see Resource Handling). -
Optionally a web fragment and CDI beans. Tobago’s
web-fragment.xmlregisters a servlet context listener and a session listener; AdminFaces' registers error pages, a filter and the PrimeFaces theme.
What differs is how much of the page the library takes over. BootsFaces leaves the page to you: you write the HTML
skeleton with h:head and h:body and use b:* tags inside. Tobago owns the whole page: the root is tc:page
and the HTML skeleton is written by the library. AdminFaces gives you a Facelets template (/admin.xhtml) and fills it
with PrimeFaces components. Knowing that tells you where a bug is likely to be: in the library’s renderer, not in your
markup.
Before a library goes into a project, six questions are worth asking; the sections that follow answer them for each library:
-
Which
jakarta.*release does its newest jar target, and does it work on 4.1 (run a page and an Ajax request)? -
When was the last release, and who answers issues?
-
What does it add to the page: bundled jQuery, Bootstrap or other frameworks, and do they clash with other libraries on the same page?
-
What does it do to security: headers, inline scripts, escaping, its own state or tokens (see Security)?
-
How much of the server-side API does it override (render kit, exception handler, view handler)?
-
What is the exit path: how many pages contain its tags?
BootsFaces
BootsFaces puts Bootstrap into Faces: b:row and b:column are the grid, b:panel, b:navBar, b:modal and
b:growl are Bootstrap widgets, and b:inputText, b:selectOneMenu and b:dataTable are form controls and a
table that render with Bootstrap classes. The 2.0.1 tag library lists, by family:
-
Layout:
container,row,column,panel,panelGrid,tabView,tab,accordion,jumbotron,well,thumbnail,carousel. -
Navigation:
navBar,navbarLinks,navLink,navCommandLink,breadcrumbs,dropMenu,flyOutMenu,pillLinks,tabLinks. -
Forms:
form,inputText,inputTextarea,inputSecret,selectOneMenu,selectMultiMenu,selectBooleanCheckbox,radiobutton,switch,spinner,slider,datepicker,dateTimePicker,colorPicker,touchSpin,commandButton,commandLink,buttonGroup. -
Feedback:
message,messages,growl,alert,modal,progressBar,label,badge. -
Data:
dataTableanddataTableColumn(the DataTables jQuery plugin),tree,fullCalendar,openStreetMap. -
Ajax helpers:
poll,remoteCommand,defaultCommand,focus,fetchBeanInfos.
Source: BootsFaces showcase and the META-INF/bootsfaces-b.taglib.xml file inside bootsfaces-2.0.1.jar
Setup and What It Adds to Your Page
BootsFaces 2.0.0 is the Jakarta release; the README says it dropped the javax namespace and that "starting with
version 2.0.0" it requires JSF 4.0 and Jakarta EE. The 2.0.1 faces-config.xml is schema version 4.0 and the POM
compiles against jakarta.faces-api 4.0.1, so Faces 4.1 is newer than anything it was built for; the pages below ran on
it. The dependency is one artifact, and Maven pulls in Guava 32.0.1 and the OWASP Java HTML sanitizer 20220608.1 as
compile dependencies.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>bookshelf-bootsfaces</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>net.bootsfaces</groupId>
<artifactId>bootsfaces</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>bookshelf</finalName>
</build>
</project>
The README also offers a java17 classifier of the same version; it was not used here. On Tomcat, add a Faces
implementation (Mojarra or MyFaces) as in Project Setup and
Getting Started; the pages in this section ran with either.
BootsFaces needs no web.xml entries, but a few context parameters tune it. The names below are the ones the
showcase’s configuration page documents, and BootsFaces_THEME with BootsFaces_USETHEME was run:
flatly stylesheets)<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.1">
<context-param>
<param-name>jakarta.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>BootsFaces_THEME</param-name>
<param-value>flatly</param-value>
</context-param>
<context-param>
<param-name>BootsFaces_USETHEME</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>facesServlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>facesServlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<request-character-encoding>UTF-8</request-character-encoding>
<response-character-encoding>UTF-8</response-character-encoding>
</web-app>
The same page documents BootsFaces_USE_VIEWPORT (the viewport meta tag), net.bootsfaces.get_jquery_from_cdn,
get_jqueryui_from_cdn, get_bootstrap_from_cdn and get_fontawesome_from_cdn (stop BootsFaces from adding its
own copy), and net.bootsfaces.defaults.renderLabel; those were read, not run. Bundled themes are the Bootswatch
ones (cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable,
sandstone, simplex, slate, spacelab, superhero, united, yeti, plus default), which is the
directory listing of the 2.0.1 jar.
What a plain page gets: with no external requests (nothing was fetched from a CDN), the head received core.css
(which starts with the comment "Bootstrap v3.4.1"), bsf.css, jquery.js (jQuery v3.7.1), bsf.js, alert.js
and tooltip.js from the bsf resource library; a page with b:dataTable also gets datatables.min.js,
pdfmake.min.js and vfs_fonts.js, and one with b:growl gets bootstrap-notify.min.js. When PrimeFaces 16 was
on the same page, BootsFaces did not emit its own jquery.js and used the one from PrimeFaces (jQuery 4.0.0); a
BootsFaces command button and a PrimeFaces one both updated the page (MyFaces 4.1.4 only). That is one small page, not a compatibility
promise: the stylesheets of the two libraries were not compared.
The Catalogue Page
The template is an ordinary Facelets template (see Templating) whose skeleton uses
BootsFaces tags. b:messages is named in the button’s update so that the message list is redrawn after each request.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:b="http://bootsfaces.net/ui">
<h:head>
<title><ui:insert name="title">Bookshelf</ui:insert></title>
</h:head>
<h:body>
<b:navBar brand="Bookshelf" brandHref="#{request.contextPath}/catalogue.xhtml" inverse="true">
<b:navbarLinks>
<b:navLink value="Catalogue" href="#{request.contextPath}/catalogue.xhtml"/>
<b:navLink value="Write a review" href="#{request.contextPath}/review.xhtml"/>
</b:navbarLinks>
</b:navBar>
<b:container>
<b:messages id="msgs"/>
<ui:insert name="content"/>
</b:container>
</h:body>
</html>
update attribute<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:b="http://bootsfaces.net/ui">
<ui:composition template="/WEB-INF/templates/layout.xhtml">
<ui:define name="title">Catalogue</ui:define>
<ui:define name="content">
<h:form id="searchForm">
<b:panel title="Search" look="primary">
<b:row>
<b:column span="8">
<b:inputText id="query" value="#{catalogueBean.query}" placeholder="Title or author"/>
</b:column>
<b:column span="4">
<b:commandButton value="Search" look="primary" icon="search"
actionListener="#{catalogueBean.search}"
update="@form :bookTable :msgs"/>
</b:column>
</b:row>
</b:panel>
</h:form>
<h:form id="bookTable">
<b:dataTable value="#{catalogueBean.books}" var="book" paginated="false" searching="false">
<b:dataTableColumn value="#{book.title}" label="Title"/>
<b:dataTableColumn value="#{book.author}" label="Author"/>
<b:dataTableColumn value="#{book.year}" label="Year"/>
</b:dataTable>
</h:form>
</ui:define>
</ui:composition>
</html>
package org.example.bookshelf.web;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
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.service.BookService;
@Named
@ViewScoped
public class CatalogueBean implements Serializable {
@Inject
private transient BookService service;
@Inject
private transient FacesContext facesContext;
private String query;
private List<Book> books;
@PostConstruct
void init() {
books = new ArrayList<>(service.search(null));
}
public List<Book> getBooks() {
return books;
}
public void search() {
books = new ArrayList<>(service.search(query));
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
books.size() + " book(s) found", null));
}
public String getQuery() { return query; }
public void setQuery(String query) { this.query = query; }
}
Source: BootsFaces showcase: Command Button, BootsFaces showcase: DataTable and BootsFaces showcase: FacesMessages
Searching for ursula replaced the table with the single matching row and showed "1 book(s) found" on both
implementations. Four things in this page are specific to BootsFaces:
-
update, notf:ajax.b:commandButtonsends the request itself. Itsupdateandprocessattributes take Faces search expressions such as@formand:bookTable(see Search Expressions); the rendered button carries an inlineonclick="BsF.ajax.callAjax(...)"handler instead of ajakarta.faces.ajaxbehaviour. -
actionListener, notaction, on Faces 4.1. That is the next section. -
The table is client side.
b:dataTablerenders every row into the HTML and attaches the DataTables plugin, which sorts, searches and pages in the browser (the rendered script calls.DataTable({...})withpaging,searchingandstateSave: true). With thousands of rows this sends all of them on every render; for large data use a server-side model, as in Data Tables and Iteration and the lazy tables of the PrimeFaces data components page. -
Input ids are prefixed. The rendered
b:inputTextis adivwith the client idsearchForm:queryaround aninputwhoseidandnameareinput_searchForm:query(both implementations). Anf:ajax rendertarget, a test selector or a hand-writtenPOSTmust use the right one.
Table cells escape their values: with a fifth test book titled <b>Bold</b> & <script>alert(1)</script> added to
BookService, the cell contained <b>Bold</b> & <script>..., shown as text.
The ActionSource2 Trap on Faces 4.1
Faces 4.1 unified two interfaces. Until 4.0, UICommand implemented ActionSource2, which added the
action method expression to the listeners that ActionSource already had; in 4.1 ActionSource declares
getActionExpression() and setActionExpression() as default methods, UICommand implements only
ActionSource, and ActionSource2 is deprecated for removal (the API says since 4.1). That is described in
Architecture and the Component Model and, for composite components,
in Composite Components.
A library written against 4.0 that asks component instanceof ActionSource2 now gets false for every UICommand.
BootsFaces 2.0.1 does exactly that: in AJAXRenderer.decode (read from the 2.0.1 bytecode, which matches the project’s
source), a button gets an ActionEvent queued only if it has ActionListener instances or is an ActionSource2
with a non-null action expression. On Faces 4.1 the second test is never true. The result, observed on Mojarra 4.1.17 and
MyFaces 4.1.4:
-
<b:commandButton action="#{trapBean.run}"/>: the request arrives, the lifecycle runs, noActionEventis created, the method is not called, and nothing is logged or shown. -
<b:commandButton actionListener="#{trapBean.runListener}"/>: called. -
<h:commandButton action="#{trapBean.run}"/>in the same form: called.
The same page on Mojarra 4.0.26 (Faces 4.0) called action on the BootsFaces button. The check is in the renderer
the BootsFaces command components share; only b:commandButton was run here.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:b="http://bootsfaces.net/ui">
<h:head><title>Action trap</title></h:head>
<h:body>
<h:form id="f">
<!-- action only: never called on Faces 4.1 -->
<b:commandButton id="viaAction" value="action" action="#{trapBean.run}"/>
<!-- actionListener: called -->
<b:commandButton id="viaListener" value="actionListener" actionListener="#{trapBean.runListener}"/>
<!-- the standard component: called -->
<h:commandButton id="standard" value="h:commandButton" action="#{trapBean.run}"/>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import java.util.logging.Logger;
@Named
@RequestScoped
public class TrapBean {
private static final Logger LOG = Logger.getLogger(TrapBean.class.getName());
public String run() {
LOG.info("TrapBean.run() was called");
return null;
}
public void runListener() {
LOG.info("TrapBean.runListener() was called");
}
}
Two remedies exist. Use actionListener on BootsFaces command components (a no-argument method worked, as in
CatalogueBean.search() above), or run on Faces 4.0 until the library is fixed. The project is not actively maintained,
so do not wait for a fix. And the general lesson is worth a tool: to find out whether an action reaches the default
action listener, decorate it. Faces lets a faces-config.xml replace the application’s ActionListener with a class
whose constructor takes the original:
package org.example.bookshelf.web;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.event.ActionListener;
import java.util.logging.Logger;
/** Development aid: logs every ActionEvent the default ActionListener receives. */
public class ActionTraceListener implements ActionListener {
private static final Logger LOG = Logger.getLogger(ActionTraceListener.class.getName());
private final ActionListener delegate;
public ActionTraceListener(ActionListener delegate) {
this.delegate = delegate;
}
@Override
public void processAction(ActionEvent event) throws AbortProcessingException {
LOG.info(() -> "ActionEvent from " + event.getComponent().getClientId());
delegate.processAction(event);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee" version="4.1">
<application>
<!-- development only: remove once the question "does my action fire?" is answered -->
<action-listener>org.example.bookshelf.web.ActionTraceListener</action-listener>
</application>
</faces-config>
Source: Jakarta Faces 4.1 API: Application (setActionListener) and Jakarta Faces 4.1 VDL: f:actionListener
With the three buttons of trap.xhtml, the log showed ActionEvent from f:viaListener and ActionEvent from f:standard
but nothing for f:viaAction, on both implementations. Remove the registration afterwards: it logs every action of the
application.
A Form With Validation and Notifications
A review form shows the input family. The labels are generated from the label attribute, a required field turns its
group red, b:message shows the message beside the field, and b:growl is a Bootstrap notification.
<!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"
xmlns:b="http://bootsfaces.net/ui">
<ui:composition template="/WEB-INF/templates/layout.xhtml">
<ui:define name="title">Write a review</ui:define>
<ui:define name="content">
<h:panelGroup id="notifications" layout="block">
<b:growl globalOnly="true" placementFrom="top" show-detail="false" allowDismiss="true" delay="4000"
escape="true"/>
</h:panelGroup>
<h:form id="reviewForm">
<b:inputText id="reviewer" value="#{reviewBean.reviewer}" label="Your name" required="true"
requiredMessage="Please tell us your name" maxlength="60"/>
<b:message for="reviewer"/>
<b:selectOneMenu id="rating" value="#{reviewBean.rating}" label="Rating">
<f:selectItem itemValue="1" itemLabel="1 - poor"/>
<f:selectItem itemValue="2" itemLabel="2"/>
<f:selectItem itemValue="3" itemLabel="3"/>
<f:selectItem itemValue="4" itemLabel="4"/>
<f:selectItem itemValue="5" itemLabel="5 - excellent"/>
</b:selectOneMenu>
<b:inputTextarea id="comment" value="#{reviewBean.comment}" label="Comment" rows="4" maxlength="500">
<f:validateLength maximum="500"/>
</b:inputTextarea>
<b:message for="comment"/>
<b:commandButton id="send" value="Send review" look="success" icon="ok"
actionListener="#{reviewBean.submit}" update="@form notifications"/>
</h:form>
</ui:define>
</ui:composition>
</html>
package org.example.bookshelf.web;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
@Named
@ViewScoped
public class ReviewBean implements Serializable {
@Inject
private transient FacesContext facesContext;
private String reviewer;
private int rating = 3;
private String comment;
public void submit() {
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
"Thanks, " + reviewer + ", for rating this book " + rating + "/5", null));
comment = null;
}
public String getReviewer() { return reviewer; }
public void setReviewer(String reviewer) { this.reviewer = reviewer; }
public int getRating() { return rating; }
public void setRating(int rating) { this.rating = rating; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
}
Submitting the empty form showed "Please tell us your name" under the field and in b:messages; submitting with a name
showed the growl "Thanks, Ana, for rating this book 3-5" on both implementations (the slash is explained below).
Observations:
-
Wrap the growl before updating it. The first version updated the
b:growlelement by its own id. When the validation failed and the growl had nothing to show, Mojarra 4.1.17 left the literal textnullin the page where the growl’s<script>had been (MyFaces did not). Putting the growl inside anh:panelGroup layout="block"and updating that panel avoided it on both implementations, which is why the page does so. -
b:growldoes not escape by default: setescape="true". The tag’s documentation says error messages encode HTML and script by default. They do not, for the growl: with theescapeattribute left out, a reviewer name ofAna <img src=x onerror="window.__pwned=1">put a realimgelement into the notification and the handler ran (window.__pwnedwas1), on Mojarra 4.1.17 and MyFaces 4.1.4. The reviewer name reaches the message throughReviewBean.submit, so this is reflected user input rendered as HTML. Withescape="true"(as in the page above) the same input was shown as text and nothing ran.b:messages, which the layout also updated in the same test, escaped the markup without being asked. -
Escaping changes the text. With
escape="true"the growl displayed3/5as3-5: the slash is replaced by a dash, on both implementations. Avoid/in growl texts, or accept the dash. -
f:validateLengthworks as a child. The standard validators attach to BootsFaces inputs like to anyEditableValueHolder(see Validators). -
Bean Validation is separate. BootsFaces has
b:fetchBeanInfosto copy Bean Validation constraints to the client; it was not run. The server side works as described in Bean Validation.
Security and Production Notes
-
Bootstrap 3 is end of life. The bundled
core.cssis Bootstrap 3.4.1. The Bootstrap project announced on 2019-07-24 that Bootstrap 3 moved to end of life and no longer receives security updates; BootsFaces ships it unchanged, and the project is not maintained actively. The components still work, but the front end is frozen. -
Inline scripts and handlers. Command buttons render
onclick="BsF.ajax.callAjax(...)"and several components emit inline<script>elements. A Content Security Policy without'unsafe-inline'(see Content Security Policy) blocks those. Neither the Mojarra nonce setting nor PrimeFaces' CSP mode is documented to cover another library’s markup. Plan for a policy that allows inline script, or do not use BootsFaces where a strict policy is required. This follows from the rendered markup and the CSP rules; the browser was not run against a strict policy. -
Transitive dependencies. Guava 32.0.1-jre and OWASP html-sanitizer 20220608.1 come with the jar. Run your dependency scanner over them: they stay at the versions the 2024 build used, and a fixed Guava or sanitizer will not arrive through a BootsFaces release. Overriding them in your own dependency management is the usual way out (not tried here).
-
Escaping. The table cells and
b:messagesescape by default (both shown above); the growl does not, as observed in the last section, so setescape="true"on it and never setescape="false"on a component that shows user text; see Cross-Site Scripting and the escape Attribute. -
Performance. The page weight of
b:dataTable(DataTables, pdfmake and a font file for the export buttons, all loaded when the table is present) is the thing to measure; see Performance.
Apache MyFaces Tobago
Tobago is an Apache MyFaces project for business applications: its stated goal is a set of components so that developers need not write HTML, CSS and JavaScript, with a theming mechanism and layout components that arrange themselves. The project page lists it as active (the 6.12.2 release is dated 2026-09-27), alongside maintenance of the 5.x line. Its compatibility table lists the 6.x line for JSF 4.0, Java 17 or higher, Servlet 6.0, and both MyFaces and Mojarra; the pages below also ran on Faces 4.1 with Tomcat 11 and Weld 6.0.4.
| Tobago | JSF | Java | Servlet |
|---|---|---|---|
4.x |
2.0 to 2.3 |
8 or higher |
3.0, 3.1 or 4.0 |
5.x |
2.3 |
17 or higher |
4.0 |
6.x |
4.0 (4.1 observed here) |
17 or higher |
6.0 |
Setup
Tobago is split into the core (tobago-core, tags, render kit, JavaScript glue) and themes (tobago-theme-standard,
-charlotteville, -roxborough, -speyside, -scarborough, all 6.12.2 on Central). The themes ship the
stylesheet and JavaScript; tobago-theme-standard is the base the others build on (a theme’s tobago-config.xml
names it as fallback). The core brings SLF4J 2.0.19, jsoup 1.23.2 and the Jakarta Annotations API; it lists
myfaces-api as an optional dependency, so the application chooses the implementation.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>bookshelf-tobago</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
<tobago.version>6.12.2</tobago.version>
<myfaces.version>4.1.4</myfaces.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.myfaces.tobago</groupId>
<artifactId>tobago-core</artifactId>
<version>${tobago.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.tobago</groupId>
<artifactId>tobago-theme-standard</artifactId>
<version>${tobago.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.tobago</groupId>
<artifactId>tobago-theme-speyside</artifactId>
<version>${tobago.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
<version>${myfaces.version}</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
<version>${myfaces.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>bookshelf</finalName>
</build>
</project>
On Mojarra, replace the two MyFaces dependencies with Mojarra’s (org.glassfish:jakarta.faces, as in
Project Setup). The project publishes a tobago-config-mojarra artifact for that case
(6.12.2 on Central), whose web fragment only registers com.sun.faces.config.ConfigureListener; on Tomcat 11 with
Mojarra 4.1.17 the catalogue page rendered with and without it, and the pages below were exercised with it. The theme is chosen in WEB-INF/tobago-config.xml; its ordering block makes
the application’s file load after the theme jars:
<?xml version="1.0" encoding="UTF-8"?>
<tobago-config
xmlns="http://myfaces.apache.org/tobago/tobago-config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://myfaces.apache.org/tobago/tobago-config http://myfaces.apache.org/tobago/tobago-config-5.1.xsd"
version="5.1">
<name>bookshelf-tobago</name>
<ordering>
<after>
<name>tobago-theme-standard</name>
<name>tobago-theme-speyside</name>
</after>
</ordering>
<theme-config>
<default-theme>speyside</default-theme>
</theme-config>
</tobago-config>
The xsi:schemaLocation URL used by the project’s own examples,
http://myfaces.apache.org/tobago/tobago-config-5.1.xsd, returned 404; the schema ships inside tobago-core and in
the source tree linked above. The web.xml and beans.xml are the plain ones of the other variants, with the Faces
servlet mapped to *.xhtml; Tobago adds a listener and a session listener through its web fragment.
Pages
A Tobago view has f:view as its root and tc:page inside it; the library writes the html, head and body
elements, including tobago-page and other custom elements, and the JavaScript is an ES module. There is no
h:head and no h:form: tc:page renders the form. Layout comes from components (tc:header, tc:bar,
tc:section, tc:flexLayout, tc:box, tc:popup) rather than from CSS classes you write.
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="jakarta.faces.core"
xmlns:tc="http://myfaces.apache.org/tobago/component">
<tc:page label="Bookshelf">
<tc:header>
<tc:bar>
<tc:link label="Catalogue" outcome="/catalogue.xhtml"/>
</tc:bar>
</tc:header>
<tc:messages/>
<tc:section label="Search">
<tc:in id="query" label="Title or author" value="#{catalogueBean.query}">
<f:facet name="after">
<tc:button label="Search" defaultCommand="true" action="#{catalogueBean.search}"/>
</f:facet>
</tc:in>
</tc:section>
<tc:sheet id="books" value="#{catalogueBean.books}" var="book" rows="10">
<tc:column label="Title" sortable="true">
<tc:out value="#{book.title}"/>
</tc:column>
<tc:column label="Author" sortable="true">
<tc:out value="#{book.author}"/>
</tc:column>
<tc:column label="Year">
<tc:out value="#{book.year}"/>
</tc:column>
</tc:sheet>
</tc:page>
</f:view>
Source: Tobago 6.12.2: tobago-example-blank and Tobago live demo
The bean is the same CatalogueBean as above. tc:button accepts both action and actionListener; action
worked on Faces 4.1 with both implementations (the ActionSource2 problem of BootsFaces does not occur), and the search of
gibson left one row and "1 book(s) found". Components with values take label and lay the label and the field out
by themselves; the 6.12.2 standard theme sets defaults such as labelLayout="flexLeft" for tc:in, tc:date and
the select components in its tobago-config.xml. A review form uses the other input components:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="jakarta.faces.core"
xmlns:tc="http://myfaces.apache.org/tobago/component">
<tc:page label="Write a review">
<tc:header>
<tc:bar>
<tc:link label="Catalogue" outcome="/catalogue.xhtml"/>
<tc:link label="Write a review" outcome="/review.xhtml"/>
</tc:bar>
</tc:header>
<tc:messages/>
<tc:section label="Your review">
<tc:in id="reviewer" label="Your name" value="#{reviewBean.reviewer}" required="true"
requiredMessage="Please tell us your name"/>
<tc:selectOneChoice id="rating" label="Rating" value="#{reviewBean.rating}">
<tc:selectItem itemValue="1" itemLabel="1 - poor"/>
<tc:selectItem itemValue="2" itemLabel="2"/>
<tc:selectItem itemValue="3" itemLabel="3"/>
<tc:selectItem itemValue="4" itemLabel="4"/>
<tc:selectItem itemValue="5" itemLabel="5 - excellent"/>
</tc:selectOneChoice>
<tc:textarea id="comment" label="Comment" value="#{reviewBean.comment}" rows="4">
<f:validateLength maximum="500"/>
</tc:textarea>
<tc:button label="Send review" actionListener="#{reviewBean.submit}"/>
</tc:section>
</tc:page>
</f:view>
Source: Tobago live demo and the META-INF/tobago.taglib.xml file inside tobago-core-6.12.2.jar
Submitting it empty showed "Please tell us your name"; with a name, "Thanks, Ana, for rating this book 3/5", on both
implementations. The tag library of 6.12.2 lists 88 tags, among them in, out, date, file, textarea,
range, stars, the select family, sheet and column, tree and its node tags, tabGroup and tab,
popup, popover, toasts, offcanvas, messages and the paginator tags. tc:file and
tc:validateFileItem exist for uploads; they were not run, so for the posture an upload needs (limits, content
sniffing, safe names) apply File Upload and Download. tc:operation (client
side commands such as showing a popup) is marked "preliminary" in its own documentation, so avoid it in code meant to
last.
The Sheet: Sorting and Paging
tc:sheet is Tobago’s data table. It has rows, a paginator, sortable columns, selection (selectable) and
lazy and lazyRows attributes for lazy loading (named in the tag library, not run). The page above sorts by title;
clicking the header sorted the five rows ascending and a second click descending on both implementations.
The first version of CatalogueBean returned the list that BookService built with List.of(...), and clicking the
header changed nothing; the server log held the unhandled warning "Cant sort the column" (Tobago’s own message text). Tobago’s
SortingUtils calls Collections.sort on the bound List itself, so an immutable list fails. The bean above copies the list
(new ArrayList<>(...)) per view, which is also the right scope: sorting a list shared by users would reorder it for
everyone.
Source: Tobago 6.12.2: SortingUtils and the tc:sheet entry of META-INF/tobago.taglib.xml
Built-In Security
Tobago ships more security defaults than the other libraries on this page. All of it was observed:
-
Response headers. Every page response fetched carried
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Cache-Control: no-cache,no-store,max-age=0,must-revalidateand aContent-Security-Policyofstyle-src 'unsafe-inline' 'nonce-...' 'self';script-src 'self';frame-ancestors 'none';. Itsscript-src 'self'means the library renders no inline script. Thetobago-config.xmlschema documentsprevent-frame-attacks,set-nosniff-headerandcontent-security-policy(with modeon,offorreport-onlyanddirectiveelements) to adjust them. -
A session secret. Each rendered form contains a hidden field
org.apache.myfaces.tobago.webapp.Secret. The schema documentscreate-session-secretandcheck-session-secret, bothtrueby default, and the library’s phase listener checks the field. A POST that carried a correct view state and the right button but no secret, or a wrong one, returned the page again with a new secret and did not run the action (no message,searchnot called) on both implementations; the same POST with the secret ran it. This is a CSRF defence on top of the view state (see Cross-Site Request Forgery). The project’s blank example turns both checks off in itstobago-config.xml; do not copy that into an application. -
Escaping and sanitizing.
tc:outescapes by default. Withescape="false"it sanitizes the HTML with jsoup (arelaxedsafelist by default; the schema documents asanitizerelement to change it), andsanitize="never"turns that off. On a string containing<b>,<script>, an<img onerror>and ajavascript:link:
<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="jakarta.faces.core"
xmlns:tc="http://myfaces.apache.org/tobago/component">
<tc:page label="Escaping and sanitizing">
<tc:out id="escaped" label="Default" value="#{rawHtmlBean.hostile}"/>
<tc:out id="sanitized" label="escape=false" value="#{rawHtmlBean.hostile}" escape="false"/>
<tc:out id="raw" label="escape=false, sanitize=never" value="#{rawHtmlBean.hostile}" escape="false"
sanitize="never"/>
</tc:page>
</f:view>
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
/** Stands for text a visitor typed: it contains markup that must never reach the page as markup. */
@Named
@RequestScoped
public class RawHtmlBean {
public String getHostile() {
return "<b>bold</b><script>alert(1)</script><img src=x onerror=alert(2)>"
+ "<a href=\"javascript:alert(3)\">link</a>";
}
}
Source: Tobago 6.12.2: tobago-config-5.1.xsd and the sanitize and escape entries of META-INF/tobago.taglib.xml
The three outputs, identical on Mojarra and MyFaces:
| Attributes | Rendered inside the output element |
|---|---|
default |
|
|
|
|
the string exactly as given, including |
A sanitizer is a second line of defence, not permission to render user HTML: keep escape="true" for anything a
visitor typed, and treat sanitize="never" as a bug unless the string is trusted markup you wrote.
Mojarra, MyFaces and Tobago
-
Render kit. Tobago’s
faces-config.xmlsetstobagoas the default render kit, and every form carries a hiddenjakarta.faces.RenderKitIdfield with that value. A page that mixesh:*components into a Tobago page was not tried; stay withtc:*tags and the standard non-visualf:*ones. The same field is why a migration to or from Tobago touches every page, and why a half-removed Tobago jar changes how plain Faces pages render. -
Both implementations ran. The same two pages worked on Mojarra 4.1.17 and MyFaces 4.1.4. The only visible difference was in markup: Mojarra wrote the page’s
<?xml ... ?>declaration before the<!DOCTYPE html>of a page that begins with one, and MyFaces did not. -
Ids. Tobago uses
::inside its generated ids (j_id_2::form,j_id_2:query::field), so selectors for tests must escape the colons, or find elements by theirtobago-*custom element. -
The exception handler.
enable-tobago-exception-handleristrueby default; it replaced the error response with a generic "An unknown error has occurred!" page in a run where a tag attribute was wrong (the cause was only in the server log). Keep the log open while developing, or turn it off intobago-config.xmlin development.
AdminFaces
AdminFaces is a family of projects on GitHub (adminfaces): admin-theme, a PrimeFaces theme styled after Bootstrap
and AdminLTE; admin-template, a Facelets admin layout (sidebar, top bar, breadcrumbs, error pages, session-expiry
handling) built on that theme; and starters for Java EE servers, Tomcat, Spring Boot with JoinFaces (the JoinFaces page) and security
variants. It is not a component library of its own: the components are PrimeFaces', and the template is a skeleton you
fill with ui:define regions (head, logo-lg, logo-mini, menu, top-menu, title, description,
body and footer, from the README).
The newest admin-template is 2.0 (2023-05-07) and its POM declares compile dependencies on PrimeFaces 12.0.0
(jakarta classifier), OmniFaces 4.1 and admin-theme 1.6.0. The README says these can be overridden. That is the
practical question for a 2026 project: PrimeFaces is at 16.0.0 (PrimeFaces setup) and OmniFaces at 5.5.5 (OmniFaces installation). Both variants were tried, with the
page below:
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:p="http://primefaces.org/ui"
template="/admin.xhtml">
<ui:define name="head">
<title>Bookshelf admin</title>
</ui:define>
<ui:define name="logo-lg">Bookshelf</ui:define>
<ui:define name="logo-mini">BS</ui:define>
<ui:define name="menu">
<ul class="sidebar-menu">
<li class="header">Catalogue</li>
<li>
<p:link outcome="/catalogue.xhtml">
<i class="fa fa-book"></i>
<span>Books</span>
</p:link>
</li>
</ul>
</ui:define>
<ui:define name="title">Books</ui:define>
<ui:define name="body">
<h:form id="searchForm">
<p:inputText id="query" value="#{catalogueBean.query}" placeholder="Title or author"/>
<p:commandButton value="Search" action="#{catalogueBean.search}" update="table"/>
</h:form>
<p:dataTable id="table" value="#{catalogueBean.books}" var="book">
<p:column headerText="Title"><h:outputText value="#{book.title}"/></p:column>
<p:column headerText="Author"><h:outputText value="#{book.author}"/></p:column>
<p:column headerText="Year"><h:outputText value="#{book.year}"/></p:column>
</p:dataTable>
</ui:define>
</ui:composition>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>bookshelf-adminfaces</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>com.github.adminfaces</groupId>
<artifactId>admin-template</artifactId>
<version>2.0</version>
<exclusions>
<!-- the template drags in PrimeFaces 12 and OmniFaces 4.1; choose the versions yourself -->
<exclusion>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
</exclusion>
<exclusion>
<groupId>org.omnifaces</groupId>
<artifactId>omnifaces</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>16.0.0</version>
</dependency>
<dependency>
<groupId>org.omnifaces</groupId>
<artifactId>omnifaces</artifactId>
<version>5.5.5</version>
</dependency>
<!-- classes the template's exception handler loads by name; on Tomcat nothing else provides them,
on a server with the EJB and Persistence APIs these two entries can be dropped -->
<dependency>
<groupId>jakarta.ejb</groupId>
<artifactId>jakarta.ejb-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>bookshelf</finalName>
</build>
</project>
Source: AdminFaces admin-template README and AdminFaces site
Observed, on both Mojarra 4.1.17 and MyFaces 4.1.4:
-
With the template’s own PrimeFaces 12.0.0 and OmniFaces 4.1 the page rendered with the admin theme (
theme.cssfrom theprimefaces-adminlibrary) and the Ajax search worked. The PrimeFaces 12 tag library only knows thehttp://primefaces.org/uinamespace: withxmlns:p="primefaces"thep:*tags were printed into the page as text. -
With PrimeFaces 16.0.0 and OmniFaces 5.5.5 substituted (the POM above) the template rendered, the sidebar was present, and the search updated the table. That was a smoke test of one page; the layout was not compared with a PrimeFaces 12 render, and untested components may differ, since the theme was written for PrimeFaces 12.
-
The template needs two APIs that Tomcat does not have. Its exception handler loads
jakarta.ejb.EJBExceptionandjakarta.persistence.OptimisticLockExceptionby name, and its web fragment declares an error page forjakarta.ejb.AccessLocalException. On Tomcat 11 with Weld, the first request ended inClassNotFoundException: jakarta.ejb.EJBExceptionand the Ajax search failed (Request return with errorin the browser console) untiljakarta.ejb-apiandjakarta.persistence-apiwere added, as in the POM. A Jakarta EE server that has both APIs needs neither entry. -
Action methods and listeners behaved normally:
p:commandButton action=...ran (PrimeFaces is not affected by the ActionSource2 change).
Treat AdminFaces as a theme-and-template kit whose upstream moves slowly: pin its version, test the exact PrimeFaces version you use, and look at the PrimeFaces components pages of this section for the components themselves.
Libraries That Ended
The JSF books of the 2000s lean on libraries whose projects have ended. None of them can be used with Faces 4.1: their
newest jars extend javax.faces classes, and Faces 3.0 renamed the packages without a compatibility layer. This section records the
evidence, so that a project that still carries them knows what it is replacing.
RichFaces
RichFaces was the JBoss component framework, with two tag libraries: a4j (page-centric Ajax: a4j:ajax,
a4j:commandButton, a4j:poll, a4j:push, a4j:jsFunction, a4j:status) and rich (self-contained
components: rich:dataTable, rich:extendedDataTable, rich:popupPanel, rich:tabPanel, rich:calendar,
rich:fileUpload, rich:autocomplete, rich:select, rich:dataScroller). The 4.5 component reference documents
all of those. The project page states that RichFaces reached end-of-life in June 2016; the last 4.x release on Maven
Central is org.richfaces:richfaces:4.5.17.Final (2016-06-20), the GitHub repository is archived (last push
2018-03-26), and the RichFaces 5 alphas on Central stopped at 5.0.0.Alpha3. The JSF 2.0 Cookbook targets
RichFaces 3.3, one major version older still; a RichFaces 3 component such as rich:modalPanel is not in the 4.5 reference, which has
rich:popupPanel instead.
ICEfaces
ICEfaces (ICEsoft) added Ajax and the ICEpush Ajax-push engine to Faces, with a component set published as the
icefaces-ace artifact. Its open-source
line stopped on Maven Central at 4.3.0 (2018-08-27); that POM depends on javax.faces and javax.servlet-api, so the
published artifacts are pre-Jakarta. The vendor site still presents ICEfaces with a commercial Enterprise Edition; whether
that edition supports Faces 4.1 is something to ask the vendor, because nothing public on Central shows it.
MyFaces Tomahawk
Tomahawk (t:, namespace http://myfaces.apache.org/tomahawk) was the MyFaces extension set from the JSF 1.1 era:
t:saveState, t:inputFileUpload, t:inputDate, t:inputCalendar, t:dataTable, t:tree2 and many more (all
present in the 1.1.14 tag library). The MyFaces site lists it under Inactive Projects (Maintenance Mode); the last
artifacts on Central (tomahawk21 1.1.14) are dated 2012-10-23.
MyFaces Trinidad
Trinidad (tr:, namespace http://myfaces.apache.org/trinidad) is the former Oracle ADF Faces: a large enterprise
component set with partial-page rendering, client-side validation, a dialog framework and pageFlowScope. It is also
under Inactive Projects (Maintenance Mode); trinidad-api 2.2.1 (2017-07-21) is the newest on Central.
Replacing Them
There is no mechanical converter; each component has a standard or PrimeFaces counterpart, and the Ajax models differ only in attribute names. The table is a map of where to look. The PrimeFaces entries link to the PrimeFaces 16 component documentation.
| Ended library | Standard Faces 4.1 | PrimeFaces 16 |
|---|---|---|
|
|
|
|
|
p:poll for timed polling |
|
|
|
|
|
|
|
|
p:dataTable with sorting, filtering, paging and lazy loading |
|
no standard dialog; a |
|
|
none (HTML5 markup plus script) |
|
|
an input with the HTML5 |
|
|
|
|
|
|
|
|
|
- |
|
none; |
Source: RichFaces 4.5 Component Reference, PrimeFaces 16 documentation and Jakarta EE Tutorial: Using Ajax with Jakarta Faces Technology
The mapping is mostly a rename. A page written with RichFaces 4 and its Faces 4.1 equivalent, both for the Bookshelf
search, show how small the Ajax part is. The RichFaces page is for recognition only (it cannot run on Faces 4.1); the
second one was run on Mojarra 4.1.17 and MyFaces 4.1.4, and the search of gibson left one row and "1 book(s) found":
<!-- RichFaces 4 (ended 2016): shown for recognition, not runnable on Faces 4.1 -->
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">
<h:body>
<h:form id="searchForm">
<h:inputText id="query" value="#{catalogueBean.query}"/>
<a4j:commandButton value="Search" action="#{catalogueBean.search}" render="results"/>
</h:form>
<rich:dataTable id="results" value="#{catalogueBean.books}" var="book">
<rich:column><f:facet name="header">Title</f:facet>#{book.title}</rich:column>
</rich:dataTable>
</h:body>
</html>
migrated.xhtml<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>Catalogue</title>
</h:head>
<h:body>
<h:form id="searchForm">
<h:inputText id="query" value="#{catalogueBean.query}"/>
<h:commandButton id="search" value="Search" action="#{catalogueBean.search}">
<f:ajax execute="@form" render=":results :messages"/>
</h:commandButton>
</h:form>
<h:messages id="messages"/>
<h:panelGroup id="results" layout="block">
<h:dataTable value="#{catalogueBean.books}" var="book">
<h:column>
<f:facet name="header">Title</f:facet>
#{book.title}
</h:column>
<h:column>
<f:facet name="header">Author</f:facet>
#{book.author}
</h:column>
</h:dataTable>
</h:panelGroup>
</h:body>
</html>
Three steps make a migration safe: replace the namespace and Ajax attributes first (the page keeps working in the old
look), then replace components one family at a time while a page-level test (see the testing page in the production
part of this section) stays green, and last remove the old jars, since two Ajax stacks on one page is how the
duplicate-request bugs of the old days came about. For the javax to jakarta step of an application as a whole, see
the migration page (see its libraries section).
Choosing
Using what was observed:
| Need | Choice | Why |
|---|---|---|
Rich components, themes, large community |
PrimeFaces (its own pages) |
Active, ships its own CSP support, Faces 4.1 in the same release train |
Bootstrap grid and forms with a few widgets, small team, short life |
BootsFaces only after a test of every component you use |
Quick to start, but Bootstrap 3, no releases since 2024 and the ActionSource2 trap above |
Security-first business application, accepts a component model of its own |
Tobago |
Headers, session secret and sanitizing built in; active releases; the cost is a different page model ( |
An admin UI with a ready sidebar and top bar |
AdminFaces' template, or build the layout on PrimeFaces' own layout components |
Fast; but pinned to the PrimeFaces 12 era in its POM and quiet upstream |
Plain Faces plus a few utilities |
Standard components with OmniFaces |
Nothing to migrate later |
A library page is only as good as the answers to the six questions in How a Library Plugs Into Faces. Three cheap tests are worth running before
adopting any library on Faces 4.1: one page with a command component using action, one Ajax update of a component
that renders a <script>, and one message or output component fed with <img src=x onerror=...>. The three surprises
in this page came from exactly those (the silent button, the growl that left null, and the growl that ran the
handler).
Mojarra and MyFaces
-
Tobago ran on both with identical rendering; the one difference noted was the XML declaration Mojarra writes before the doctype. Tobago’s project lists both implementations as supported.
-
BootsFaces ran on both. The
actionproblem is the same on both because it comes from the interface change, and the growlnulltext appeared only on Mojarra. -
AdminFaces ran on both, with PrimeFaces 12 and with 16.
-
A library that decorates the
ActionListener,ViewHandlerorExceptionHandlerbehaves differently when two of them are on the classpath; AdminFaces installs an exception handler of its own, which is the reason it needed the EJB and Persistence classes.
What Changed in Which Version
-
Faces 3.0: the
javax.facespackages becamejakarta.faces; every library that was not rebuilt stopped working. The libraries that ended (RichFaces, ICEfaces, Tomahawk, Trinidad) never were. -
Faces 4.0: JSP and several old APIs were removed. BootsFaces 2.0.0 and Tobago 6.0.0 are the releases for it (BootsFaces' README: Faces 4.0 and Jakarta EE required; Tobago’s table: 6.x lists JSF 4.0).
-
Faces 4.1:
ActionSourceandActionSource2unified, the latter deprecated for removal. A library written for 4.0 can compile and run on 4.1 and still misbehave (BootsFaces). -
Faces 5.0 (upcoming): only a milestone as of this writing (
5.0.0-M9); the deprecatedActionSource2may be removed from it, and none of the libraries here was tried on it. Plan the library audit when 5.0 is released. -
BootsFaces: 0.x to 1.5 on
javax; 2.0.0 and 2.0.1 on Jakarta, Java 11 (2.0.1 on 2024-09-02). -
Tobago: 4.x (JSF 2.x, Java 8), 5.x (JSF 2.3, Java 17), 6.x (JSF 4.0, Java 17, Bootstrap 5 based).
-
AdminFaces:
admin-template1.6.1 targets Jakarta EE 9.1 with PrimeFaces 11 (jakartaclassifier) and offers alegacyclassifier forjavax; 2.0 (2023-05-07) targets Jakarta EE 10 with PrimeFaces 12.
Where the Books Are Outdated
The JSF 2.0 Cookbook (2010) is where the libraries of this page appear most: its recipes target JSF 2.0 with
RichFaces 3.3, PrimeFaces 2.0, Tomahawk, Trinidad, Seam 2 and Spring 2.5, and about half of them rely on an extension library. Today
RichFaces, Tomahawk and Trinidad are ended or in maintenance mode, Seam 2 and Spring 2.5 are long gone, and the standard
solutions replaced many of those recipes (file upload with h:inputFile, Ajax with f:ajax, scopes with CDI). Use the
tables above to map any recipe to its present counterpart. JavaServer Faces 2.0: The Complete Reference covers the
standard component library and extension points but predates every library here, and the render-kit chapters of Pro JSF
and Ajax (2006) are about rich-component frameworks of the JSF 1.x era (the skin and render kit mechanisms of ICEfaces,
Trinidad and RichFaces); the render-kit theory still holds, the libraries do not. The PrimeFaces Cookbook and Learning
PrimeFaces Extensions Development cover PrimeFaces and PrimeFaces Extensions, not these libraries, and are discussed on the
PrimeFaces pages. None of the books mentions BootsFaces, Tobago or AdminFaces, which appeared after or alongside them.
Pitfalls
-
Trusting "supports Jakarta EE 10" for Faces 4.1. Test
action, an Ajax update and a<script>-rendering component (The ActionSource2 Trap on Faces 4.1). -
b:commandButton action=on Faces 4.1. Silently never called; useactionListener. -
b:growlwithoutescape="true". It renders user text in messages as HTML; set the attribute. -
Updating
b:growlby its own id. Wrap it in a panel and update the panel. -
b:dataTablewith large lists. The whole list is rendered and sorted in the browser. -
Immutable lists in
tc:sheet. Sorting fails with an unhandled warning; copy the list per view. -
Turning off Tobago’s session secret or its sanitizer. The blank example does the first; do not copy it. Never use
sanitize="never"with user input. -
Copying the
xsi:schemaLocationof Tobago’s examples. The URL does not resolve. -
AdminFaces on Tomcat. Add the EJB and Persistence APIs, or errors turn into
ClassNotFoundException. -
Two jQuery versions. BootsFaces skipped its copy when PrimeFaces' was present; other combinations were not tried.
-
Mixing component libraries on one page without testing. Stylesheets collide even when scripts do not.
-
Keeping an ended library "for now". Each year the gap to Jakarta Faces widens; plan the replacement before the next upgrade of the server, not during it.
References
-
BootsFaces: Quick Start (written for JSF 2.2 and 2.3 and the
java.sun.comnamespaces; do not follow its version statements) -
Jakarta EE Tutorial: Using Ajax with Jakarta Faces Technology
-
Leonard: JSF 2.0 Cookbook, Packt, 2010, publisher page
-
Jacobi, Fallows: Pro JSF and Ajax: Building Rich Internet Components, Apress, 2006, publisher page
-
Burns, Schalk: JavaServer Faces 2.0: The Complete Reference, McGraw-Hill, 2010, publisher page