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.

Table 1. Libraries beyond PrimeFaces and OmniFaces
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 admin-template

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

javax.faces, JSF 2.x

ICEfaces

4.3.0 (2018-08-27, Maven Central)

Still offered commercially by its vendor; no open-source release after 2018 on Central

javax.faces and javax.servlet dependencies

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)

javax.faces, former Oracle ADF Faces

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 under http://bootsfaces.net/ui (not a jakarta.faces.* URN: the old-style URI is still how it is used), Tobago 6.12.2 declares its tags under http://myfaces.apache.org/tobago/component.

  • A faces-config.xml in META-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, on PreRenderViewEvent; it adds the library’s CSS and JavaScript to the page head. Tobago’s file sets tobago as 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 the ResourceHandler (see Resource Handling).

  • Optionally a web fragment and CDI beans. Tobago’s web-fragment.xml registers 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: dataTable and dataTableColumn (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.

pom.xml — the BootsFaces variant of Bookshelf (the application server supplies Faces)
<?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:

web.xml — BootsFaces parameters (the theme was checked: the page loaded the 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.

WEB-INF/templates/layout.xhtml
<!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>
catalogue.xhtml — a search form and a table, updated with BootsFaces' own 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>
CatalogueBean.java
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; }
}

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, not f:ajax. b:commandButton sends the request itself. Its update and process attributes take Faces search expressions such as @form and :bookTable (see Search Expressions); the rendered button carries an inline onclick="BsF.ajax.callAjax(...)" handler instead of a jakarta.faces.ajax behaviour.

  • actionListener, not action, on Faces 4.1. That is the next section.

  • The table is client side. b:dataTable renders every row into the HTML and attaches the DataTables plugin, which sorts, searches and pages in the browser (the rendered script calls .DataTable({...}) with paging, searching and stateSave: 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:inputText is a div with the client id searchForm:query around an input whose id and name are input_searchForm:query (both implementations). An f:ajax render target, a test selector or a hand-written POST must 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 &lt;b&gt;Bold&lt;/b&gt; &amp; &lt;script&gt;..., 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, no ActionEvent is 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.

trap.xhtml — three buttons, one silent
<!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>
TrapBean.java
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:

ActionTraceListener.java and its registration — a development aid
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>

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.

review.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core"
      xmlns:ui="jakarta.faces.facelets"
      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>
ReviewBean.java
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:growl element by its own id. When the validation failed and the growl had nothing to show, Mojarra 4.1.17 left the literal text null in the page where the growl’s <script> had been (MyFaces did not). Putting the growl inside an h:panelGroup layout="block" and updating that panel avoided it on both implementations, which is why the page does so.

  • b:growl does not escape by default: set escape="true". The tag’s documentation says error messages encode HTML and script by default. They do not, for the growl: with the escape attribute left out, a reviewer name of Ana <img src=x onerror="window.__pwned=1"> put a real img element into the notification and the handler ran (window.__pwned was 1), on Mojarra 4.1.17 and MyFaces 4.1.4. The reviewer name reaches the message through ReviewBean.submit, so this is reflected user input rendered as HTML. With escape="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 displayed 3/5 as 3-5: the slash is replaced by a dash, on both implementations. Avoid / in growl texts, or accept the dash.

  • f:validateLength works as a child. The standard validators attach to BootsFaces inputs like to any EditableValueHolder (see Validators).

  • Bean Validation is separate. BootsFaces has b:fetchBeanInfos to 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.css is 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:messages escape by default (both shown above); the growl does not, as observed in the last section, so set escape="true" on it and never set escape="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.

Table 2. Tobago lines (from the project’s version table)
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.

pom.xml — the Tobago variant of Bookshelf on MyFaces (builds the WAR; its dependencies are the jars the Tomcat 11 runs used)
<?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:

WEB-INF/tobago-config.xml
<?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.

catalogue.xhtml — header, search and a sheet
<?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>

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:

review.xhtml
<?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-revalidate and a Content-Security-Policy of style-src 'unsafe-inline' 'nonce-...' 'self';script-src 'self';frame-ancestors 'none';. Its script-src 'self' means the library renders no inline script. The tobago-config.xml schema documents prevent-frame-attacks, set-nosniff-header and content-security-policy (with mode on, off or report-only and directive elements) to adjust them.

  • A session secret. Each rendered form contains a hidden field org.apache.myfaces.tobago.webapp.Secret. The schema documents create-session-secret and check-session-secret, both true by 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, search not 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 its tobago-config.xml; do not copy that into an application.

  • Escaping and sanitizing. tc:out escapes by default. With escape="false" it sanitizes the HTML with jsoup (a relaxed safelist by default; the schema documents a sanitizer element to change it), and sanitize="never" turns that off. On a string containing <b>, <script>, an <img onerror> and a javascript: link:

html.xhtml — three ways to render the same hostile string
<?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>
RawHtmlBean.java
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

&lt;b&gt;bold&lt;/b&gt;&lt;script&gt;...: everything as text

escape="false"

<b>bold</b><img><a>link</a>: the script element is gone, onerror and the javascript: href are removed

escape="false" sanitize="never"

the string exactly as given, including <script>, onerror and the javascript: link

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.xml sets tobago as the default render kit, and every form carries a hidden jakarta.faces.RenderKitId field with that value. A page that mixes h:* components into a Tobago page was not tried; stay with tc:* tags and the standard non-visual f:* 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 their tobago-* custom element.

  • The exception handler. enable-tobago-exception-handler is true by 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 in tobago-config.xml in 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:

catalogue.xhtml — the Bookshelf catalogue in the admin template
<?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>
pom.xml — admin-template with PrimeFaces 16 and OmniFaces 5 (builds the WAR; run on Tomcat 11)
<?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>

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.css from the primefaces-admin library) and the Ajax search worked. The PrimeFaces 12 tag library only knows the http://primefaces.org/ui namespace: with xmlns:p="primefaces" the p:* 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.EJBException and jakarta.persistence.OptimisticLockException by name, and its web fragment declares an error page for jakarta.ejb.AccessLocalException. On Tomcat 11 with Weld, the first request ended in ClassNotFoundException: jakarta.ejb.EJBException and the Ajax search failed (Request return with error in the browser console) until jakarta.ejb-api and jakarta.persistence-api were 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.

Table 3. From the ended libraries to Faces 4.1
Ended library Standard Faces 4.1 PrimeFaces 16

a4j:ajax, a4j:commandButton render=

f:ajax execute= render= (see Ajax)

p:ajax and p:commandButton update=

a4j:poll, a4j:push, ICEpush

f:websocket and @Push for server push (see WebSocket Push)

p:poll for timed polling

a4j:jsFunction

f:ajax from a script with faces.ajax.request

p:remoteCommand

a4j:status

faces.ajax.addOnEvent (see the JavaScript API in the Ajax page)

p:ajaxStatus

rich:dataTable, rich:extendedDataTable, rich:dataScroller, t:dataTable

h:dataTable, ui:repeat (see Data Tables and Iteration)

p:dataTable with sorting, filtering, paging and lazy loading

rich:popupPanel, Trinidad’s dialog framework

no standard dialog; a div shown by script, or the PrimeFaces component

p:dialog

rich:tabPanel

none (HTML5 markup plus script)

p:tabView

rich:calendar, t:inputCalendar, t:inputDate

an input with the HTML5 type="date" through pass-through attributes (see HTML5-Friendly Markup) and f:convertDateTime or a java.time converter

p:datePicker

rich:fileUpload, t:inputFileUpload

h:inputFile (see File Upload and Download)

p:fileUpload

rich:autocomplete, rich:select

h:inputText with f:ajax listeners

p:autoComplete

t:saveState, Trinidad’s pageFlowScope

@ViewScoped, @FlowScoped (see Scopes and Faces Flows)

-

t:tree2

none; ui:repeat for a nested list

p:tree

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":

Before: RichFaces 4 (not runnable on Faces 4.1)
<!-- 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>
After: standard Faces 4.1 — 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:

Table 4. Choosing a component library for Faces 4.1
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 (tc:page, its render kit)

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 action problem is the same on both because it comes from the interface change, and the growl null text appeared only on Mojarra.

  • AdminFaces ran on both, with PrimeFaces 12 and with 16.

  • A library that decorates the ActionListener, ViewHandler or ExceptionHandler behaves 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.faces packages became jakarta.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: ActionSource and ActionSource2 unified, 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 deprecated ActionSource2 may 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-template 1.6.1 targets Jakarta EE 9.1 with PrimeFaces 11 (jakarta classifier) and offers a legacy classifier for javax; 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; use actionListener.

  • b:growl without escape="true". It renders user text in messages as HTML; set the attribute.

  • Updating b:growl by its own id. Wrap it in a panel and update the panel.

  • b:dataTable with 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:schemaLocation of 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.