PrimeFaces: Getting Started, Configuration and Themes

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 PrimeFaces 16.0.0 (published 2026-09-04; 15.0.18 is the previous line), Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2), Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4, with Java 17 or later for the application. PrimeFaces itself needs only a Java 11 runtime (its class files are version 55, checked in the 16.0.0 jar) and a Faces 4.0 or newer implementation: version 16 removed the last javax.* support. Faces 4.0 behaves like 4.1 for everything on this page. The versions were checked on 2026-10-08.

Everything marked as observed was run in a scratch Bookshelf WAR on Apache Tomcat 11.0.26 (embedded) with Weld 6.0.4.Final, once with Mojarra 4.1.17 and once with MyFaces 4.1.4; the hello-world page and the theme switch were also run on Mojarra 4.0.26. Computed styles, fonts and widths were read with headless Chrome 155. Servers other than Tomcat (WildFly, GlassFish, Payara, Open Liberty, TomEE) were not run for this page. Statements that rest only on the PrimeFaces documentation or on the 16.0.0 source are said so. Data tables, Ajax and forms, dialogs, file upload and charts have their own pages in this section; here they appear only where a setting of this page changes them.

What PrimeFaces Adds to Faces

PrimeFaces is a component library for Faces: tags in the p: namespace (176 tags in the 16.0.0 tag library, from p:accordionPanel to p:wizard), a theme system, a JavaScript layer built on jQuery that every component talks to, and a set of replacements for parts of the standard machinery. What the 16.0.0 jar registers on its own (read from its META-INF/faces-config.xml and web-fragment.xml) is the reason a page with no explicit setup already behaves differently:

Registration Effect

Renderers for jakarta.faces.Head and jakarta.faces.Body

h:head and h:body are rendered by PrimeFaces: they inject the theme, the icon font, the component styles and scripts, the locale script and the client settings (The Head: What PrimeFaces Writes and in Which Order). A page without h:head receives none of it.

PrimeResourceHandler (resource handler)

Wraps resource handling: PrimeFaces resource URLs carry the parameter v=16.0.0 (observed on the ln=primefaces URLs and absent on the application’s own ln=css URLs; The Context Parameters, HIDE_RESOURCE_VERSION), and the streamed content of components such as p:graphicImage is served through it.

PrimeFacesContextFactory, PrimePartialViewContextFactory

PrimeFaces’s own faces-context and partial-view-context factories, installed through the standard <factory> elements.

Phase listeners AutoUpdatePhaseListener, CspPhaseListener, DialogKeepFlashPhaseListener

Named after p:autoUpdate, the Content Security Policy support and the flash handling of the dialog framework.

Validators for the ids jakarta.faces.Length, jakarta.faces.LongRange, jakarta.faces.DoubleRange, jakarta.faces.RegularExpression, and twenty converters

Classes in org.primefaces.validate and org.primefaces.convert registered under the standard ids, so once the jar is on the class path every f:validateLength or standard converter of the application is served by a PrimeFaces class. Client-side validation is on the the PrimeFaces Ajax, forms and validation page of this section.

PrimeServletContextListener, UploadedFileCleanerListener, <distributable/>, font MIME mappings (ttf, woff, woff2, eot, svg)

Declared in the jar’s web-fragment.xml, so a servlet container needs no web.xml entry for them.

The PrimeFaces jar also contains a beans.xml with bean-discovery-mode="all", so a CDI container scans it. On a servlet container that has no Bean Validation or Jakarta Persistence API on the class path, Weld 6.0.4 logged 36 WELD-000119 lines at INFO level ("Not generating any bean definitions from org.primefaces... because of underlying class loading error: Type jakarta.validation... not found"). The application worked; the lines only mean that the optional PrimeFaces features that need those APIs are off (The Dependency and the Optional Libraries).

The Dependency and the Optional Libraries

PrimeFaces is one jar, org.primefaces:primefaces. Its published POM has no compile-scope dependency: mvn dependency:tree on the project below lists the jar and nothing under it. The libraries PrimeFaces can use are declared provided or optional, so each one reaches the WAR only if the application asks for it.

pom.xml (excerpt) — PrimeFaces, the extra themes and the libraries the Bookshelf pages use
<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <primefaces.version>16.0.0</primefaces.version>
</properties>

<dependencies>
  <!-- Faces, CDI and Servlet: provided by the server (see the project setup page for Tomcat) -->
  <dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>11.0.0</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>org.primefaces</groupId>
    <artifactId>primefaces</artifactId>
    <version>${primefaces.version}</version>
  </dependency>

  <!-- every theme except saga-blue; versioned together with PrimeFaces -->
  <dependency>
    <groupId>org.primefaces</groupId>
    <artifactId>primefaces-themes</artifactId>
    <version>${primefaces.version}</version>
  </dependency>

  <!-- required by p:textEditor (and by the official hello-world page below) -->
  <dependency>
    <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
    <artifactId>owasp-java-html-sanitizer</artifactId>
    <version>20260101.1</version>
  </dependency>

  <!-- optional icon font and grid, delivered as WebJars (see the icons and responsive sections) -->
  <dependency>
    <groupId>org.webjars</groupId>
    <artifactId>font-awesome</artifactId>
    <version>6.7.2</version>
  </dependency>
  <dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>primeflex</artifactId>
    <version>4.0.0</version>
  </dependency>
</dependencies>

The runtime class path of exactly this POM (mvn dependency:copy-dependencies) was primefaces 16.0.0, primefaces-themes 16.0.0, font-awesome 6.7.2, primeflex 4.0.0 and the sanitizer 20260101.1 with its java8-shim and java10-shim jars. Other server-side libraries are needed only by one component each. The versions below are the ones the PrimeFaces documentation says were tested (the 16.0.0 POM leaves the library versions to its parent, so they were not independently checked):

Library Coordinates Tested version Needed by

Bean Validation implementation

for example Hibernate Validator

1.1 or later

the Bean Validation features (see the PrimeFaces Ajax and forms page)

Apache POI

org.apache.poi:poi, poi-ooxml

5.5.1

p:dataExporter to Excel

OpenPDF

com.github.librepdf:openpdf

1.4.2

p:dataExporter to PDF

RSS reader

com.apptasticsoftware:rssreader

3.12.0

p:feedReader

Okapi barcode

uk.org.okapibarcode:okapibarcode

0.5.2

p:barcode

OWASP Java HTML Sanitizer

com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer

20260101.1

p:textEditor

XDEV Chart.js Java model

software.xdev:chartjs-java-model

2.9.0

p:chart with the Java model

The Faces runtime. The PrimeFaces 16 download page lists Jakarta Faces "4.0 - 5.0" as the supported range, and the migration guide says the baseline is now Faces 4.0. The dependencies page still says "2.3, 3.0, 4.0" and "Servlet 3.0+"; that table is stale (flagged). The 16.0.0 jar’s faces-config.xml is a 4.0 document, and the pages of this section ran on Mojarra 4.0.26, Mojarra 4.1.17 and MyFaces 4.1.4. Faces 5.0 is still a milestone (jakarta.faces-api 5.0.0-M9); the download page already lists it, but nothing here was run against it.

No server module. On WildFly, GlassFish, Payara, Open Liberty and TomEE the jar travels inside the WAR like any other dependency. The jar’s web-fragment.xml declares <distributable/>; the deployment and clustering page discusses clustering.

Optional libraries fail loudly. A component whose library is missing does not degrade silently:

The PrimeFaces hello-world page, run without the sanitizer on the class path (both implementations)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core"
      xmlns:p="primefaces">
    <f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
        <h:head></h:head>
        <h:body>
            <p:textEditor />
        </h:body>
    </f:view>
</html>

Without owasp-java-html-sanitizer this page, which is the one in the official guide, answered HTTP 500 on Mojarra and on MyFaces: "TextEditor component is marked secure='true' but the HTML Sanitizer was not found on the classpath. Either add the HTML sanitizer to the classpath per the documentation or mark secure='false' if you would like to use the component without the sanitizer." With the dependency from the POM above it rendered on both. The component sanitises the HTML it receives, because rich text typed by a user is untrusted markup. The error message offers secure="false"; do not use it for text that other users will see.

The Namespace and a First Page

The 16.0.0 jar ships two tag libraries for the same components: primefaces.urn.taglib.xml, namespace primefaces, and primefaces.taglib.xml, namespace http://primefaces.org/ui. Both are discovered automatically and both worked on Mojarra 4.1.17, MyFaces 4.1.4 and Mojarra 4.0.26. The documentation uses the short form, in line with the jakarta.faces.* URNs of Faces 4.0 and later; the long form is what every older tutorial and book shows and keeps working.

xmlns:p="primefaces"
xmlns:p="http://primefaces.org/ui"

The smallest page that exercises the theme, the icon font, the component styles and the Ajax script is a form with one input and one button:

hello.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:p="primefaces">
<h:head>
  <title>Hello PrimeFaces</title>
</h:head>
<h:body>
  <h1>Bookshelf</h1>
  <h:form>
    <p:inputText placeholder="Search"/>
    <p:commandButton value="Go" icon="pi pi-search"/>
  </h:form>
</h:body>
</html>

The response head of that page, in the order the markup arrives (Mojarra; identical for MyFaces apart from the client ids), was: theme.css from the library primefaces-saga-blue, primeicons/primeicons.css, components.css, jquery/jquery.js, core.js, components.js, jquery/jquery-plugins.js, locales/locale-en.js, then a script that assigns PrimeFaces.settings. The button rendered <button ... class="ui-button ui-widget ui-state-default ui-button-text-icon-left" with an ui-icon pi pi-search span. Nothing in the page, in web.xml or in faces-config.xml asked for any of that.

The h:head of a PrimeFaces application is rendered by org.primefaces.renderkit.HeadRenderer, which replaces the standard renderer of the type jakarta.faces.Head. Its class comment states the intended order: the first facet, the theme style sheet, the PrimeIcons style sheet, the middle facet, the registered resources, the client validation scripts, the locale script, the PrimeFaces.settings script, the initialisation scripts, the content of the h:head element and the last facet. The three facets are PrimeFaces additions to h:head; the standard element knows no facets.

Reading the response shows how that works in practice, because it differs in one detail from the comment. In the output the theme and the icon style sheet come with the registered resources and not before the middle facet, so markup in the middle facet arrives before theme.css. The probe page puts one plain link and one h:outputStylesheet into each facet and into the head:

order-css.xhtml — a probe for the order of style sheets
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="jakarta.faces.core"
      xmlns:h="jakarta.faces.html"
      xmlns:p="primefaces">
<h:head>
  <f:facet name="first">
    <link rel="stylesheet" href="#{resource['css:link-first.css']}"/>
    <h:outputStylesheet library="css" name="res-first.css"/>
  </f:facet>
  <title>Resource order</title>
  <h:outputStylesheet library="css" name="res-body.css"/>
  <link rel="stylesheet" href="#{resource['css:link-body.css']}"/>
  <f:facet name="middle">
    <link rel="stylesheet" href="#{resource['css:link-middle.css']}"/>
    <h:outputStylesheet library="css" name="res-middle.css"/>
  </f:facet>
  <f:facet name="last">
    <link rel="stylesheet" href="#{resource['css:link-last.css']}"/>
    <h:outputStylesheet library="css" name="res-last.css"/>
  </f:facet>
</h:head>
<h:body>
  <h:form><p:inputText/></h:form>
</h:body>
</html>

The style sheets arrived in this order (observed; res-* are the h:outputStylesheet resources, link-* the plain links, all in the library or directory css):

# Mojarra 4.1.17 MyFaces 4.1.4

1

link-first

link-first

2

link-middle

link-middle

3

theme.css (ln=primefaces-saga-blue)

theme.css

4

primeicons/primeicons.css

primeicons/primeicons.css

5

res-body

res-body

6

res-last

res-first

7

res-first

res-middle

8

res-middle

res-last

9

components.css (added by p:inputText)

components.css

10

link-body

link-body

11

link-last

link-last

What follows from it:

  • A plain element in the first, middle or last facet or directly in h:head is written where the order above puts it. An h:outputStylesheet is a resource component: wherever it is written (in a facet too) it joins the list of registered resources and is written there. A facet therefore does not move a resource component. On Mojarra the four resource components arrived in the order body, last, first, middle; on MyFaces in the order they were declared. The probe page differs only by that. Do not rely on the relative order of resources declared in different facets.

  • The application’s h:outputStylesheet resources arrived before components.css (the likely reason is that the PrimeFaces component that pulls components.css in sits in the body and is processed after the head; the order itself was observed). At equal specificity components.css and the theme therefore win over a style sheet declared with h:outputStylesheet.

  • The plain link elements in h:head or in the last facet arrived after the PrimeFaces style sheets and the scripts, so their rules win. The expression #{resource['css:link-last.css']} (library css, name link-last.css) produces the right URL for a resource library; see resource identifiers.

  • h:outputScript is different. In a separate run (both implementations) a script written in the first facet was written in that facet, ahead of theme.css, and scripts written in h:head and in the last facet came after the PrimeFaces scripts, in the places of the plain elements.

flowchart TB start["h:head encodeBegin: PrimeFaces HeadRenderer"] first["first facet: plain markup"] middle["middle facet: plain markup"] reg["registered resources: theme.css, primeicons.css, application style sheets, components.css, jquery.js, core.js, components.js, locale script"] settings["PrimeFaces.settings script and initialisation scripts"] children["children of h:head: plain markup, h:outputScript"] last["last facet: plain markup, then the closing head tag"] start --> first --> middle --> reg --> settings --> children --> last

The consequence for styling is the layout the Bookshelf uses: the application’s own rules go into a plain link in the last facet, and they are written to beat the theme by specificity, not by position:

WEB-INF/templates/layout.xhtml (head excerpt) and resources/css/bookshelf.css (one rule)
<h:head>
  <f:facet name="first">
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
  </f:facet>
  <title>#{msg['app.name']}</title>
  <ui:insert name="head"/>
  <f:facet name="last">
    <link rel="stylesheet" href="#{resource['css:bookshelf.css']}"/>
  </f:facet>
</h:head>
.ui-button.bookshelf-accent { background: #6f42c1; border-color: #6f42c1; }

The selector has two classes, so it beats the theme’s .ui-button rule whatever the order of the files. In headless Chrome the button computed to rgb(111, 66, 193), the accent colour, and not to the Saga Blue rgb(33, 150, 243).

A page without h:head gets nothing. The renderers are attached to the standard tags. A page that uses the plain HTML elements <head> and <body> rendered the PrimeFaces component markup but no link and no script of PrimeFaces on Mojarra and on MyFaces: no theme, no icons, no widgets. Use h:head and h:body in every template and view that holds PrimeFaces components. Faces keeps one renderer per type, so a second library that registers its own jakarta.faces.Head renderer would replace the PrimeFaces one; that case was not tested, so check such a combination in a test.

Moving the scripts to the end of the body. primefaces.MOVE_SCRIPTS_TO_BOTTOM set to true removed every script element from the head and wrote all of them, the PrimeFaces resources, the settings script and the initialisation scripts included, at the end of the body (observed on the hello page: six script elements in the head and two inline widget scripts in the body without the parameter; none in the head and six at the end of the body with it, five resource scripts and one inline script; both widgets of the page were created in all three modes). The value defer additionally gave each resource script a defer="defer" attribute and merged the initialisation scripts into one DOMContentLoaded handler that removes its own element. The page paints before the scripts run, which is the performance advantage the PrimeFaces performance page names (see the PrimeFaces part of the performance page). Test inline scripts of your own that call PrimeFaces or jQuery, because they now run before the library is loaded.

Where the Setup Sits in the Request Processing Lifecycle

The setup features of this page touch the lifecycle (the six phases) in few places, and knowing which ones explains the theme behaviour above:

Moment What happens

Application start

The context parameters are read; PostConstructApplicationEvent and PreDestroyApplicationEvent listeners of the jar (PostConstructApplicationEventListener, PreDestroyApplicationEventListener) are registered in its faces-config.xml. A changed parameter needs a restart: every test of this page restarted the application after changing one. Only the expression in primefaces.THEME is evaluated again on each request.

Restore View to Process Validations

Nothing of this page. A theme chooser is an ordinary input: the value is converted and validated like any other (a posted theme that is not in the menu stopped here with a validation error).

Update Model Values

PrimeThemeBean.setName runs, so the new theme is already in the bean when the next phase starts.

Invoke Application

PrimeThemeBean.apply runs and returns the outcome with faces-redirect=true; the lifecycle ends with a redirect and no Render Response.

Render Response

Everything else. HeadRenderer.encodeBegin evaluates primefaces.THEME and writes the head resources, the locale script and the settings (the settings gain projectStage:'Development' when the project stage is not Production, observed for Development); encodeEnd writes the last facet. The three phase listeners that PrimeFaces registers (AutoUpdatePhaseListener, CspPhaseListener, DialogKeepFlashPhaseListener) are all registered for the Render Response phase (source).

The Context Parameters

PrimeFaces needs no configuration ("configuration by exception"). Everything that can be configured is a context-param in web.xml named primefaces.NAME. The table lists every name the 16.0.0 jar reads (the 22 names of the documentation’s configuration table, plus CACHE_PROVIDER, PUBLIC_CAPTCHA_KEY and PRIVATE_CAPTCHA_KEY that belong to the cache and captcha components, and the three standard parameters it also reads). Parameters are read once when the application starts, with two things to know about expressions: HeadRenderer evaluates the value of THEME as a value expression on every request, and the configuration evaluates the value of CSP as an expression once, while it is built (source). The last column says what was run.

Parameter Default Effect Checked

THEME

saga-blue

Theme library; none writes no theme link; saga, arya and vela are accepted as short names; a value expression is evaluated on every request (Themes)

observed

PRIME_ICONS

true

Writes primeicons/primeicons.css; false removes it

observed

FLEX

false

Components with a responsive mode use PrimeFlex classes instead of Grid CSS (p:selectOneRadio layout="responsive" changed from ui-g ui-g-12 ui-md-6 to grid col-12 md:col-6; p:panelGrid did not change)

observed

DIR

ltr

rtl adds ui-spinner-rtl, ui-accordion-rtl and similar classes to the components that support it

observed

TOUCHABLE

true

Switches touch support of data table, galleria, tab view, carousel and context menu off globally; the value may be an expression

documentation

HIDE_RESOURCE_VERSION

false

true drops v=16.0.0 from the URLs of PrimeFaces resources

observed

MOVE_SCRIPTS_TO_BOTTOM

false

true or defer: all scripts at the end of the body (The Head: What PrimeFaces Writes and in Which Order)

observed

SUBMIT

full

partial serialises only the components that are processed, not the whole form, in Ajax requests; components can override it with partialSubmit (partialSubmit:true appeared in PrimeFaces.settings)

observed, effect on traffic documented

RESET_VALUES

false

Ajax-updated inputs are always reset

documentation

EARLY_POST_PARAM_EVALUATION

false

Makes p:ajax behave like f:ajax for queued Ajax requests (the documentation links PrimeFaces issue 109); shows as earlyPostParamEvaluation:true in the settings

source

CLIENT_SIDE_VALIDATION

false

Client-side validation; adds moment/moment.js

source

INTERPOLATE_CLIENT_SIDE_VALIDATION_MESSAGES

false

Client-side validation messages come from the server’s message interpolator

documentation

TRANSFORM_METADATA

false

Bean Validation metadata becomes HTML attributes

documentation

MARK_INPUT_AS_INVALID_ON_ERROR_MSG

false

An input that receives an error FacesMessage is marked invalid (red border) when it is updated on the client

documentation

CLIENT_SIDE_LOCALISATION

true (the documentation table says false)

Writes locales/locale-<language>.js for the current locale (Localisation)

observed, source

COOKIES_SAME_SITE

Strict

SameSite of the cookies written by PrimeFaces' JavaScript (cookiesSameSite:'Strict' in the settings)

observed

CSP, CSP_POLICY, CSP_REPORT_ONLY_POLICY

false

Content Security Policy: true, reportOnly, policyProvided

see the security page

EXCEPTION_TYPES_TO_IGNORE_IN_LOGGING

empty

Comma separated exception classes the PrimeFaces exception handler does not log

see the exception handling page

MULTI_VIEW_STATE_STORE

session

Where the multi-view state is kept: session or client-window

documentation

DYNAMIC_CONTENT_LIMIT

200

Limit of streamed dynamic contents per view (source default)

source

CACHE_PROVIDER

none

Cache provider of p:cache (the component’s documentation)

documentation

PUBLIC_CAPTCHA_KEY, PRIVATE_CAPTCHA_KEY

none

Keys of p:captcha; keep them out of the repository (Common Mistakes)

documentation

Three standard parameters change PrimeFaces behaviour as well, because it reads them: jakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL (reported to the client as considerEmptyStringNull), jakarta.faces.VALIDATE_EMPTY_FIELDS (auto validates empty fields when Bean Validation is present, reported as validateEmptyFields; on the Tomcat scratch app without Bean Validation it was false) and jakarta.faces.validator.DISABLE_DEFAULT_BEAN_VALIDATOR. They are described on the configuration page of this section (validation parameters).

The documentation table is out of date on one default. CLIENT_SIDE_LOCALISATION is listed with the default false; the PrimeFaces 14 migration guide says it "is now enabled by default" and the 16.0.0 source parses the missing parameter as true (Objects.toString(value, "true")). Observed: without the parameter, a request with Accept-Language: es for an application that supports es wrote locales/locale-es.js; with the explicit value false it wrote no locale script (flagged).

The web.xml the Bookshelf uses for the PrimeFaces-specific part is short. The theme is an expression (Themes), the scripts move to the bottom, partial submit is on, and the CSP is left at its default here because the security page develops it:

web.xml (excerpt) — the PrimeFaces parameters of the Bookshelf
<!-- the theme of the current user; see the themes section -->
<context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>#{primeThemeBean.name}</param-value>
</context-param>

<!-- send only what p:ajax processes -->
<context-param>
    <param-name>primefaces.SUBMIT</param-name>
    <param-value>partial</param-value>
</context-param>

<!-- scripts at the end of the body, deferred -->
<context-param>
    <param-name>primefaces.MOVE_SCRIPTS_TO_BOTTOM</param-name>
    <param-value>defer</param-value>
</context-param>

<!-- no version in resource URLs -->
<context-param>
    <param-name>primefaces.HIDE_RESOURCE_VERSION</param-name>
    <param-value>true</param-value>
</context-param>

HIDE_RESOURCE_VERSION removes v=16.0.0 from every PrimeFaces resource URL, which keeps the exact library version out of the HTML. The documentation notes that it can be argued whether the version number is a security risk; it also removes the cache buster, so after an upgrade a browser that cached components.js may keep the old file until its cache expires (the resource handling page covers the cache headers: caching headers).

Parameters and filters that are not in the table: the org.primefaces.webapp.filter.CharacterEncodingFilter (UTF-8 unless an encoding init parameter says otherwise) and org.primefaces.webapp.filter.NoCacheFilter (no-cache headers for views) are filters the application maps to the FacesServlet when it wants them (documentation, not run). The request-character-encoding and response-character-encoding elements of a Servlet 6.1 web.xml do the first job without a filter.

Themes

A PrimeFaces theme is a resource library named primefaces-<name> that holds one file, theme.css. HeadRenderer takes the value of primefaces.THEME (default saga-blue), evaluates it as a value expression, and writes a link to theme.css in the library primefaces- plus that value. The theme is a style sheet for the ui-* classes that components.css and the renderers use, and it defines the colours, the fonts and the CSS custom properties (--surface-a, --primary-color, --text-color and others) of the page.

The themes that ship

Only saga-blue is inside primefaces-16.0.0.jar. Every other theme is in the separate primefaces-themes jar (since PrimeFaces 15.0.0, when the themes moved out of the main jar), which has the same version number as PrimeFaces. The 16.0.0 themes jar holds the theme libraries below (listed from the jar), plus the shared libraries primefaces-aura-common (fonts) and primefaces-material-light-common, which are not themes, and a directory primefaces-mytheme that looks like a sample (its primary colour is #2196F3, as in Saga Blue).

Family Theme names (value of primefaces.THEME)

Saga

saga-blue (white surface, in the main jar; the default)

Vela

vela-blue (dark surface)

Arya

arya-blue (dark surface)

Aura

aura-light-emerald, aura-dark-emerald

Bootstrap 4

bootstrap4-blue-light, bootstrap4-blue-dark, bootstrap4-purple-light, bootstrap4-purple-dark

Material

material-indigo-light, material-indigo-dark, material-deeppurple-light, material-deeppurple-dark

Material compact

material-compact-indigo-light, material-compact-indigo-dark, material-compact-deeppurple-light, material-compact-deeppurple-dark

Luna

luna-blue, luna-green, luna-amber, luna-pink

Nova

nova-light, nova-dark, nova-colored

Saga, Vela and Arya are the base light and dark themes. In the 16.0.0 jars their surface colour (--surface-a) was #ffffff (Saga Blue), #1f2d40 (Vela Blue) and #1e1e1e (Arya Blue), and their primary colour (--primary-color) #2196F3, #90CAF9 and #90CAF9. Neither a theme’s name nor its CSS says reliably whether it is dark: vela-blue, nova-dark and aura-dark-emerald all declare color-scheme:light in their CSS. Choose a theme by looking at it in the PrimeFaces showcase (which refused scripted requests, see the flags) and by testing the contrast of your own pages.

Choosing a theme

A fixed theme is one context-param:

web.xml (excerpt) — a fixed theme
<context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>vela-blue</param-value>
</context-param>

The behaviour of the values, run on Tomcat 11.0.26 with the primefaces-themes jar on the class path:

Value Result

absent

theme.css.xhtml?ln=primefaces-saga-blue&v=16.0.0

vela, saga (and arya)

Short names still work in 16.0.0: the link used ln=primefaces-vela-blue and ln=primefaces-saga-blue, and the file was served (HTTP 200, text/css); arya is mapped the same way in the source. The 15.0.0 migration guide announced that the old names would go in the next version, but the 16.0.0 source still maps them

none

No theme link at all (the page is unstyled by the theme; components.css is still written)

#{primeThemeBean.name}

Evaluated for every request (next section)

a name that does not exist (missing-theme) or an empty value

Mojarra: <link rel="stylesheet" href="RES_NOT_FOUND" />, no message in the Tomcat log and no error page. MyFaces: no theme link, no message. Either way the page loads unstyled

A typo in the theme name therefore produces no error on either implementation: the page just looks wrong. If a theme from the primefaces-themes jar is selected and the jar is not on the class path, the result is the same as for a missing name.

A theme per user

Because HeadRenderer evaluates the parameter as an expression on every request, the theme can come from a bean. The Bookshelf keeps the choice in a session-scoped bean (a user preference, not page state) and persists nothing else; a real application would store it in the user’s profile or in a preference cookie (cookie attributes).

PrimeThemeBean.java
package org.example.bookshelf.web;

import jakarta.enterprise.context.SessionScoped;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
import java.util.Set;

/** Holds the PrimeFaces theme chosen by the current user. */
@Named
@SessionScoped
public class PrimeThemeBean implements Serializable {

    private static final Set<String> ALLOWED = Set.of("saga-blue", "vela-blue", "arya-blue", "luna-green");

    private String name = "saga-blue";

    public List<String> getChoices() {
        return ALLOWED.stream().sorted().toList();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (ALLOWED.contains(name)) {
            this.name = name;
        }
    }

    /** Redirects to the current view so that the next response is built with the new theme only. */
    public String apply() {
        String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
        return viewId + "?faces-redirect=true";
    }
}
web.xml (excerpt) and the form in the layout
<context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>#{primeThemeBean.name}</param-value>
</context-param>
<h:form id="themeForm" styleClass="bookshelf-theme">
  <p:outputLabel for="theme" value="#{msg['theme.label']}"/>
  <p:selectOneMenu id="theme" value="#{primeThemeBean.name}">
    <f:selectItems value="#{primeThemeBean.choices}"/>
  </p:selectOneMenu>
  <p:commandButton value="#{msg['theme.apply']}" icon="pi pi-palette"
                   action="#{primeThemeBean.apply}" ajax="false"/>
</h:form>

Three things in the bean are deliberate. The name goes into a resource library name, so it comes from a fixed list: the setter ignores any other value, and the menu rejects it earlier (a posted evil produced a validation error, HTTP 200, and left the theme at saga-blue). The action returns the current view id with faces-redirect=true, and the form uses ajax="false". The reason is the next table, which is the main pitfall of a per-user theme:

Response to the theme form Theme links in the HTML (both implementations)

302 to the same view (faces-redirect=true), then GET

one: ln=primefaces-vela-blue

200, the same view rendered again by the postback

two: ln=primefaces-vela-blue, then ln=primefaces-saga-blue

After an ordinary postback that keeps the view the old theme link came back as well, after the new one, so the old theme wins the cascade. The page showed the previous theme although the bean held the new one (the saved view is the likely source of the old link; that was not verified). Redirecting to a fresh view, which is Post/Redirect/Get as on the flash and Post/Redirect/Get page, gave one link.

sequenceDiagram participant B as Browser participant F as FacesServlet participant T as primeThemeBean B->>F: POST themeForm with theme=vela-blue F->>T: setName vela-blue (Update Model Values) F->>T: apply (Invoke Application) T-->>F: outcome /catalogue.xhtml?faces-redirect=true F-->>B: 302 Location /bookshelf/catalogue.xhtml B->>F: GET /catalogue.xhtml F->>T: getName while h:head is rendered F-->>B: HTML with theme.css ln=primefaces-vela-blue

Changing the theme without leaving the page. PrimeFaces has a client-side function for it, PrimeFaces.changeTheme(name), which replaces the library name in the href of the theme link. The 10.0.0 migration guide shows it as the replacement of the removed ThemeSwitcher component, with an ordinary menu:

themes-ajax.xhtml — a menu that swaps the theme link in the browser
<h:form id="frmTheme">
  <p:selectOneMenu id="themeSelectMenu" widgetVar="themeMenu" value="#{primeThemeBean.name}">
    <f:selectItems value="#{primeThemeBean.choices}"/>
    <p:ajax process="@this"
            oncomplete="PrimeFaces.changeTheme($('#frmTheme\\:themeSelectMenu_label').text());"/>
  </p:selectOneMenu>
</h:form>

In headless Chrome, choosing vela-blue in the opened menu sent the value through p:ajax (a later request of the same session rendered ln=primefaces-vela-blue) and changed the link in the open page; the button changed from rgb(33, 150, 243) to rgb(144, 202, 249). Calling PrimeFaces.changeTheme('vela-blue') from the console changes the page in the same way. The function changes only the browser; the next full page request uses whatever the server-side expression returns, which is why the menu above also binds the value to the bean.

A custom theme

There are two routes, and the first one can be run in minutes.

Extend a ready-made theme. A theme is just a style sheet in a library named primefaces-<name>, and Faces evaluates value expressions inside style sheet resources (expressions inside resource files). A theme can therefore import another theme and override rules after it:

src/main/webapp/resources/primefaces-bookshelf/theme.css
@import url("#{resource['primefaces-saga-blue:theme.css']}");

/* Bookshelf brand colour on top of Saga Blue */
.ui-button:not(.ui-button-secondary) { background: #6f42c1; border-color: #6f42c1; }
.ui-button:not(.ui-button-secondary):enabled:hover { background: #5a32a3; border-color: #5a32a3; }
web.xml (excerpt)
<context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>bookshelf</param-value>
</context-param>

On Mojarra and MyFaces the request for the theme returned the file with @import url("/bookshelf/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-saga-blue&v=16.0.0"); in its first line, and in headless Chrome a p:commandButton computed to rgb(111, 66, 193), the brand colour. This costs a second request for the imported file and it relies on the selectors of the imported theme (the :not(...) guard keeps the secondary buttons of the theme intact); it is fine for small adjustments and for a prototype.

Build a theme from the sources. A production theme is compiled from SCSS. The primefaces-themes module of the PrimeFaces repository holds theme-base (the layout and structure of all components) and themes (one folder per theme with _variables.scss, _extensions.scss and a theme.scss that imports the variables, the base and the extensions). The 16.0.0 files of Saga Blue start like this, and the build is mvn dart-sass:compile-sass (README of the module):

primefaces-themes/themes/primefaces-saga-blue/theme.scss and the head of _variables.scss (PrimeFaces 16.0.0 source)
@import './variables';
@import '../../theme-base/_components';
@import './_extensions';

/* _variables.scss */
$primaryColor: #2196F3 !default;
$primaryTextColor: #ffffff !default;
$shade000: #ffffff !default;    // surface
$shade700: #495057 !default;    // text color

A new theme is a new folder with its own variables, built by the same Maven plugin into META-INF/resources/primefaces-<name>/theme.css in a jar or a resources/primefaces-<name>/theme.css folder in the WAR; the _extensions.scss file is the place for customisations (its comment says "Customizations to the designer theme should be defined here"). The PrimeFaces documentation also points to a community template repository that creates a theme without forking the whole module. The SCSS route was read from the sources and not compiled for this page. The theme designer, the visual tool that produces such SCSS themes (the comment in _extensions.scss speaks of "the designer theme"), could not be examined: the vendor’s web site answers scripted requests with a bot-protection page that this work does not bypass, so the tool is not described here; both points are listed in the flags.

Themes and components that no longer exist

  • ThemeRoller themes. Until PrimeFaces 14 the themes were jQuery UI ThemeRoller style sheets, distributed as the org.primefaces.themes artifacts and picked from a gallery. The 15.0.0 migration guide states that "Legacy ThemeRoller themes are no longer supported and will not render correctly". A theme name from a book such as bluesky or cupertino does not exist in 16.

  • p:themeSwitcher. Deprecated in 10.0.0 and removed in 11.0.0; it is not among the 176 tags of the 16.0.0 tag library. Use a menu and PrimeFaces.changeTheme or the per-user bean above.

  • The aristo and omega themes were removed in 10.0.0 and saga became the default; the setting primefaces.THEME_FORMS disappeared in 3.1.

Icons: PrimeIcons and Font Awesome

PrimeIcons is the icon font of the PrimeTek libraries. It is part of the PrimeFaces jar (the font in five formats under primefaces/vendor/primeicons/fonts and primeicons/primeicons.css), HeadRenderer includes it by default, and the parameter primefaces.PRIME_ICONS set to false removes the style sheet (observed). Icons are CSS classes, pi plus pi-<name>, used on any element or in the icon attribute of components such as p:commandButton:

PrimeIcons on a heading and on buttons (and Font Awesome next to them)
<h1><i class="pi pi-book"></i> PrimeIcons and <i class="fa-solid fa-book"></i> Font Awesome</h1>
<h:form id="actions">
  <p:commandButton id="pi" value="Search" icon="pi pi-search" type="button"/>
  <p:commandButton id="fa" value="Borrow" icon="fa-solid fa-book" type="button"/>
  <p:commandButton id="only" icon="pi pi-trash" type="button" title="Delete"/>
</h:form>

The button with icon="pi pi-search" rendered <span class="ui-button-icon-left ui-icon ui-c pi pi-search"> before its label, and in headless Chrome the ::before content of the icon used the font family primeicons, which the browser reported as loaded. A button without a value and with a title rendered the title as its text (ui-button-icon-only), which gives an icon-only button an accessible name; give every icon-only control a title or an aria-label.

Font Awesome was removed from the PrimeFaces jar in 11.0.0. The documentation says PrimeFaces supports Font Awesome 4.x, 5.x and 6.x or later added by the application as a WebJar. The icons are used the same way (icon="fa-solid fa-book", or a plain <i>) once the style sheet is on the page. The style sheet is the part that needs care, and the check below was run on both implementations:

The layout of a page that loads Font Awesome as a WebJar (icons.xhtml)
<ui:composition template="/WEB-INF/templates/layout.xhtml">
  <ui:define name="head">
    <h:outputStylesheet library="webjars" name="font-awesome/6.7.2/css/all.min-jsf.css"/>
  </ui:define>
  ...
</ui:composition>
WebJar and style sheet Files named *-jsf.css in the WebJar Font in headless Chrome (Mojarra and MyFaces)

font-awesome 6.7.2, css/all.min-jsf.css

yes

loaded

font-awesome 7.2.0, css/all.min-jsf.css

yes

loaded

font-awesome 7.2.0, css/all.min.css

yes

error (the font file was not found)

font-awesome 7.3.0, css/all.min.css

no (the latest WebJar at the time of writing has none)

error

The -jsf variants exist because the font URLs inside the standard style sheet are relative (../webfonts/...), and the URL of a resource served by Faces carries the library as a query parameter (?ln=webjars), which a relative URL cannot express (that is the likely reason; the failure itself, a font in the state error, was observed). The -jsf style sheets replace those URLs with value expressions (the 6.7.2 file contains url("#{resource['webjars:font-awesome/6.7.2/webfonts/fa-brands-400.woff2']}&#")) and Faces evaluates them. The practical rules: use the *-jsf.css file, and before upgrading the WebJar check that it still contains the -jsf files (the 7.3.0 WebJar does not). Pin the version in the library path, as font-awesome/6.7.2/... does; the version is part of the resource name.

Responsive Layout: ui-fluid, Grid CSS, PrimeFlex and panelGrid

The PrimeFaces documentation names three ingredients of a responsive page: the page layout (menu, header, content, footer, which is plain HTML and CSS of the application), a grid framework that places the content, and components that adapt to their container. PrimeFaces gives the last two a vocabulary.

Fluid components

Form components have a fixed width by default. Putting them in a container with the class ui-fluid makes them take the full width of the container, which is what a grid cell needs (p:selectOneMenu has autoWidth, whose default auto since 13.0.0 notices a parent ui-fluid and skips its own width calculation). Complex components such as p:dialog, charts, the carousel and the pick list have a responsive attribute that makes them react to the screen size (documentation, not run). Touch features (swipe on the tab view, long press for the context menu) are on by default and primefaces.TOUCHABLE switches them off globally.

Grid CSS

Grid CSS is a 12-column fluid grid built into components.css. A container has the class ui-g; its columns have ui-g-1 to ui-g-12, ui-g-offset-N moves a column, ui-g-nopad removes the padding, and the prefixes ui-sm-N, ui-md-N, ui-lg-N and ui-xl-N override the width above or below a breakpoint. The breakpoints are in the 16.0.0 components.css:

Prefix Media query in components.css Device named in the documentation

ui-sm-*

max-width:40em

phones

ui-md-*

min-width:40.063em

tablets

ui-lg-*

min-width:64.063em

desktops

ui-xl-*

min-width:90.063em

big screens

Four columns on large screens, two on tablets, one on phones
<div class="ui-g">
    <div class="ui-g-12 ui-md-6 ui-lg-3">one</div>
    <div class="ui-g-12 ui-md-6 ui-lg-3">two</div>
    <div class="ui-g-12 ui-md-6 ui-lg-3">three</div>
    <div class="ui-g-12 ui-md-6 ui-lg-3">four</div>
</div>

The older Grid CSS of the PrimeFaces 5 era, ui-grid, ui-grid-row and ui-grid-col-N (with ui-grid-responsive), is still in the 16.0.0 components.css and still used by some renderers (the default p:panelGrid wraps its rows in ui-grid ui-grid-responsive). The 10.0.0 migration guide deprecated Grid CSS in favour of PrimeFlex, and the 15.0.0 guide deleted the "grid" layouts of some components, so new layouts should use PrimeFlex unless a component needs Grid CSS.

PrimeFlex (the flex grid)

The flex-based grid, "Flex Grid CSS" in the PrimeFaces documentation, is PrimeFlex, a library shared by PrimeFaces, PrimeNG and PrimeReact. It is not in the PrimeFaces jar. The documentation adds it as a WebJar and includes the style sheet from the webjars library:

pom.xml (excerpt) and the style sheet in the head of a page
<dependency>
  <groupId>org.webjars.npm</groupId>
  <artifactId>primeflex</artifactId>
  <version>4.0.0</version>
</dependency>
<h:head>
  <title>Grid</title>
  <h:outputStylesheet library="webjars" name="primeflex/4.0.0/primeflex.min.css"/>
</h:head>
<h:body>
  <div class="grid">
    <div class="col-12 md:col-6">one</div>
    <div class="col-12 md:col-6">two</div>
  </div>
</h:body>

With the WebJar of PrimeFlex 4.0.0 on the class path the stylesheet was served (the library name webjars is the directory META-INF/resources/webjars of the jar) and the container computed to display:flex with flex-wrap:wrap. PrimeFaces 16 uses PrimeFlex 4.0.0, whose colour variables are --p-red-500 instead of --red-500 (16.0.0 migration guide); PrimeFlex 3 had changed the class names already (PrimeFaces 11 and 12 migration guides), and PrimeFaces ships a CLI tool to migrate pages from PrimeFlex 2 to 3.

The parameter primefaces.FLEX tells the components that have a responsive mode to emit PrimeFlex classes instead of Grid CSS classes. Observed: p:selectOneRadio layout="responsive" rendered class="ui-grid ui-grid-responsive" over ui-g and ui-g-12 ui-md-6 cells without the parameter, and class="ui-grid-responsive" over grid and col-12 md:col-6 cells with FLEX=true. A p:panelGrid layout="grid" did not change (its layout is chosen by the attribute). The documentation says "not implemented by all components yet"; the 16.0.0 attributes flex exist on p:dataGrid, p:dataView, p:datePicker, p:orderList, p:selectManyCheckbox and p:selectOneRadio.

p:panelGrid layouts

p:panelGrid is the layout component for forms. Its layout attribute selects the markup; the rendered output for the same two cells in each mode was (class names from the run; the columns value must be a divisor of 12 for the grid layouts since 13.0.0):

layout Rendered structure Needs

tabular

<table role="grid"> with tr and td

nothing

grid (the default since 13.0.0; without the attribute the output was this one)

div.ui-panelgrid-content.ui-grid.ui-grid-responsive containing div.ui-g rows and cells ui-g-12 ui-md-6

Grid CSS (in components.css)

flex

div.ui-panelgrid-content.grid containing cells col-12 md:col-6

PrimeFlex

tailwind

div.ui-panelgrid-content.grid.grid-cols-1.sm:grid-cols-2.gap-4 containing cells ui-panelgrid-cell

Tailwind CSS (the application provides it)

columnClasses sets the classes of the cells, and styleClass="ui-noborder" (or ui-panelgrid-blank) removes the borders. A form with the labels at a quarter of the width on tablets and larger, and the fields at three quarters, is written once with Grid CSS and once with PrimeFlex:

panelGrid with Grid CSS classes (the Bookshelf search form)
<div class="ui-fluid">
  <p:panelGrid columns="2" layout="grid" styleClass="ui-noborder"
               columnClasses="ui-g-12 ui-md-3, ui-g-12 ui-md-9">
    <p:outputLabel for="title" value="Title"/>
    <p:inputText id="title"/>
    <p:outputLabel for="author" value="Author"/>
    <p:inputText id="author"/>
  </p:panelGrid>
</div>
panelGrid with PrimeFlex classes (the PrimeFlex style sheet is included in the head)
<div class="ui-fluid">
  <p:panelGrid columns="2" layout="flex" styleClass="ui-noborder"
               columnClasses="col-12 md:col-3, col-12 md:col-9">
    <p:outputLabel for="title" value="Title"/>
    <p:inputText id="title"/>
    <p:outputLabel for="author" value="Author"/>
    <p:inputText id="author"/>
  </p:panelGrid>
</div>

In headless Chrome at a window width of 1280 pixels the two cells of a row measured 316 and 948 pixels (Grid CSS) and 320 and 960 pixels (PrimeFlex), a quarter and three quarters; at 756 pixels, below the tablet breakpoint, every cell was the full 756 pixels wide in both. The documentation warns that labels may not line up with the fields in horizontal forms and suggests align-items:center on .ui-panelgrid-content through a style class.

Localisation

PrimeFaces has two places for localised text, the server-side message bundles and the client-side locale scripts, and both are chosen from the Faces locale of the view.

The Faces locale. Which locale PrimeFaces uses is the locale of the view. An application has to declare the locales it supports in faces-config.xml: in the scratch application, without <supported-locale>es</supported-locale> a request with Accept-Language: es was answered with locale:'en_US' in PrimeFaces.settings; with it, the settings carried locale:'es' (see locale selection).

faces-config.xml (excerpt)
<application>
  <locale-config>
    <default-locale>en</default-locale>
    <supported-locale>es</supported-locale>
  </locale-config>
  <resource-bundle>
    <base-name>org.example.bookshelf.messages</base-name>
    <var>msg</var>
  </resource-bundle>
</application>

Server-side messages. The messages of the components (validation, empty table, file limits) live in org/primefaces/Messages.properties in the jar, with a translation per language: the 16.0.0 jar holds Messages_<language>.properties for Czech, German, Greek, Spanish, Persian, French, Croatian, Hindi, Indonesian (in), Italian, Japanese, Georgian, Korean, Lithuanian, Latvian, Dutch, Norwegian, Polish, Portuguese, Romanian, Russian, Slovak, Swedish, Turkish and Chinese, and an English file. A language that is missing is added, and a shipped message is changed, by putting a file of the same name in WEB-INF/classes (or in an application jar earlier on the class path):

src/main/resources/org/primefaces/Messages_es.properties — the empty-table text of the Bookshelf
primefaces.data.EMPTY_MESSAGE=Todav\u00eda no hay libros en el cat\u00e1logo.
empty.xhtml — an empty table
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html" xmlns:p="primefaces">
<h:head><title>empty</title></h:head>
<h:body>
  <p:dataTable id="t" value="#{emptyBean.books}" var="b">
    <p:column headerText="Title"><h:outputText value="#{b}"/></p:column>
  </p:dataTable>
</h:body>
</html>
EmptyBean.java — a bean that returns no rows
package org.example.bookshelf.web;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import java.util.List;

@Named
@RequestScoped
public class EmptyBean {
    public List<String> getBooks() {
        return List.of();
    }
}

Observed on Mojarra and on MyFaces: with the file, Accept-Language: en gave "No records found." and Accept-Language: es gave "Todavía no hay libros en el catálogo."; without it the shipped Spanish text "No se encontraron registros." appeared. The \uXXXX escapes in the file work with any file encoding.

Client-side texts. The widgets (date picker, filter menus, paginator tips, upload buttons) read their texts and the names of days and months from a JavaScript object, PrimeFaces.locales['<language>'], defined by locales/locale-<language>.js of the library primefaces. The 16.0.0 jar contains 48 of them, with English as the default. With primefaces.CLIENT_SIDE_LOCALISATION enabled, which is the default (The Context Parameters), the head renderer adds the script of the current language, and nothing else is needed:

<!-- observed for Accept-Language: es, no parameter in web.xml -->
<script src="/bookshelf/jakarta.faces.resource/locales/locale-es.js.xhtml?ln=primefaces&v=16.0.0"></script>

A page can also load one script by hand, <h:outputScript name="locales/locale-nl.js" library="primefaces"/>, for a language that the Faces locale does not select. A language PrimeFaces does not ship is defined in your own script by assigning a complete object to PrimeFaces.locales['xx']; the documentation lists the keys of the English object (day and month names, firstDayOfWeek, the filter and paginator texts). The client scripts and the server bundles are independent: translate both.

Putting It Together: the Bookshelf Shell

The pieces above make the shell of the PrimeFaces variant of the Bookshelf catalogue (the earlier pages of this section build the plain Faces version). The template carries the head settings, the theme chooser and the message area; a page defines the content. Compared with the plain-Faces template of the templating page (templating) the differences are the p: tags, the PrimeIcons in the header and the last facet:

src/main/webapp/WEB-INF/templates/layout.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="jakarta.faces.core"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets"
      xmlns:p="primefaces">
<f:view contentType="text/html" encoding="UTF-8">
  <h:head>
    <f:facet name="first">
      <meta charset="UTF-8"/>
      <meta name="viewport" content="width=device-width, initial-scale=1"/>
    </f:facet>
    <title>#{msg['app.name']}</title>
    <ui:insert name="head"/>
    <f:facet name="last">
      <link rel="stylesheet" href="#{resource['css:bookshelf.css']}"/>
    </f:facet>
  </h:head>
  <h:body>
    <header class="bookshelf-header">
      <span class="bookshelf-brand"><i class="pi pi-book"></i> #{msg['app.name']}</span>
      <h:form id="themeForm" styleClass="bookshelf-theme">
        <p:outputLabel for="theme" value="#{msg['theme.label']}"/>
        <p:selectOneMenu id="theme" value="#{primeThemeBean.name}">
          <f:selectItems value="#{primeThemeBean.choices}"/>
        </p:selectOneMenu>
        <p:commandButton value="#{msg['theme.apply']}" icon="pi pi-palette"
                         action="#{primeThemeBean.apply}" ajax="false"/>
      </h:form>
    </header>
    <main>
      <p:messages id="messages" showDetail="true" closable="true"/>
      <ui:insert name="content"/>
    </main>
  </h:body>
</f:view>
</html>
src/main/webapp/catalogue.xhtml — a fluid search form and the first table of the PrimeFaces catalogue
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="jakarta.faces.core"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets"
      xmlns:p="primefaces">
<ui:composition template="/WEB-INF/templates/layout.xhtml">
  <ui:define name="content">
    <h1>#{msg['catalogue.title']}</h1>
    <h:form id="search">
      <div class="ui-fluid">
        <p:panelGrid columns="2" layout="grid" styleClass="ui-noborder"
                     columnClasses="ui-g-12 ui-md-3, ui-g-12 ui-md-9">
          <p:outputLabel for="q" value="#{msg['catalogue.search']}"/>
          <p:inputText id="q" value="#{param.q}"/>
        </p:panelGrid>
      </div>
      <p:commandButton value="#{msg['catalogue.search.button']}" icon="pi pi-search"
                       styleClass="bookshelf-accent" ajax="false"/>
    </h:form>
    <p:dataTable id="books" 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>
</html>
messages.properties and messages_es.properties (excerpt)
app.name=Bookshelf
catalogue.title=Catalogue
catalogue.search=Search titles
catalogue.search.button=Search
theme.label=Theme
theme.apply=Apply

# messages_es.properties
app.name=Estanter\u00eda
catalogue.title=Cat\u00e1logo
catalogue.search=Buscar t\u00edtulos
catalogue.search.button=Buscar
theme.label=Tema
theme.apply=Aplicar

On Mojarra 4.1.17 and MyFaces 4.1.4 the catalogue answered HTTP 200 with four table rows, the theme form switched the theme through the redirect (the theme link changed from saga-blue to arya-blue), and the accent button computed to rgb(111, 66, 193) in headless Chrome. The search field is a plain value read from #{param.q} and only shows the form. The other PrimeFaces pages of this section (PrimeFaces Ajax, forms and validation; PrimeFaces data components; PrimeFaces panels, menus and dialogs; PrimeFaces files, media, charts and drag and drop) replace the table with a lazy model, the ajax="false" button with p:ajax, and add dialogs, menus and uploads to this shell.

Checking the Setup from the Command Line

The failures of this page are silent (an unstyled page, a font that does not load), so check the result instead of trusting the browser’s first impression. The three requests below are enough; the output is from the Tomcat scratch application with primefaces.THEME set to vela-blue (the ;jsessionid= parts disappear once the client sends the session cookie):

Checking the theme link, the resource and the client settings
# which theme and which PrimeFaces style sheets does the page link?
curl -s http://localhost:8080/bookshelf/catalogue.xhtml | grep -o 'href="[^"]*\(theme\|primeicons\|components\)\.css[^"]*"'
# href="/bookshelf/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-vela-blue&amp;v=16.0.0"
# href="/bookshelf/jakarta.faces.resource/primeicons/primeicons.css.xhtml?ln=primefaces&amp;v=16.0.0"
# href="/bookshelf/jakarta.faces.resource/components.css.xhtml?ln=primefaces&amp;v=16.0.0"

# is the theme served?
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download}\n' \
  'http://localhost:8080/bookshelf/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-vela-blue&v=16.0.0'
# 200 text/css 218530
curl -s -o /dev/null -w '%{http_code}\n' \
  'http://localhost:8080/bookshelf/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-nosuch&v=16.0.0'
# 404

# what does the client script believe?
curl -s http://localhost:8080/bookshelf/catalogue.xhtml | grep -o 'PrimeFaces.settings={[^}]*}'
# PrimeFaces.settings={locale:'en',viewId:'/catalogue.xhtml',contextPath:'\/bookshelf',cookiesSecure:false,cookiesSameSite:'Strict',validateEmptyFields:false,considerEmptyStringNull:false}

The same three checks belong in an integration test of the application (the testing page builds on them; PrimeFaces Selenium drives the PrimeFaces widgets). In a browser, PrimeFaces.settings and the network tab of the developer tools show the same facts, and document.fonts lists the icon fonts and whether each one is loaded or in error, which is how the Font Awesome table above was produced.

Where the Other PrimeFaces Topics Are

This page stops at the shell of the application. The components and their behaviour are on the pages that follow in this group: PrimeFaces Ajax, forms and validation (p:ajax, process and update with their search expressions (see also PrimeFaces search expressions), partial submit, p:remoteCommand, the input components, client-side validation, p:growl and p:messages); PrimeFaces data components (p:dataTable, LazyDataModel, trees, data export); PrimeFaces panels, menus and dialogs (panels, tab view, menus, p:dialog and the dialog framework); PrimeFaces files, media, charts and drag and drop; PrimeFaces Extensions; OmniFaces; and the page about other component libraries (BootsFaces, Tobago and the discontinued libraries of the older books). Where PrimeFaces runs is covered by Tomcat and Weld, Jakarta EE servers, Spring Boot with JoinFaces and Quarkus. A reader who needs one of those first can start there and come back here for the theme and the configuration.

Security Settings of the Setup

The setup decisions of this page have security consequences that the later PrimeFaces pages build on:

  • Content Security Policy. primefaces.CSP switches PrimeFaces to nonce-stamped scripts and registered handlers instead of inline code. The security page ran it (p:commandButton worked on Mojarra and MyFaces) and explains the limitations (Content Security Policy). Moving the scripts to the bottom (The Head: What PrimeFaces Writes and in Which Order) does not replace it.

  • Rich text is untrusted markup. p:textEditor refuses to render without the OWASP sanitizer unless secure="false" is set (The Dependency and the Optional Libraries). Leave secure at its default for anything another user will read. The component documentation states the sanitizer is there "to prevent XSS".

  • The theme name is a resource name. A theme taken from a request parameter, a cookie or a profile reaches HeadRenderer as a library name. Validate it against a fixed list as PrimeThemeBean does (A theme per user) and store only the name.

  • Version disclosure. v=16.0.0 in every PrimeFaces URL tells an attacker the exact library version. HIDE_RESOURCE_VERSION removes it (The Context Parameters); keep the library patched either way.

  • Cookies. COOKIES_SAME_SITE (Strict by default) covers the cookies PrimeFaces' JavaScript writes. The session cookie and the cookies of the Faces implementation have their own settings, described on the security page.

  • Keys and credentials. PUBLIC_CAPTCHA_KEY and PRIVATE_CAPTCHA_KEY are context-param values in the documentation’s examples. The private key is a secret: keep it out of the repository and of web.xml in version control, and give it to the server through its own deployment configuration. This page has no example key on purpose.

  • Uploads. p:fileUpload has limits and validators of its own; the upload and download page sets the posture (size limits, content sniffing, file name sanitising) that the PrimeFaces files page reuses (upload limits, validating the content).

  • View state. Components are ordinary Faces components, so the rule of the security page applies: view state protects against cross-site request forgery only with server-side state saving (cross-site request forgery).

Mojarra and MyFaces Compared

PrimeFaces is written for both implementations and the pages of this section behaved the same on both, apart from the following (all observed with Mojarra 4.1.17 and MyFaces 4.1.4 on Tomcat 11.0.26 and Weld 6.0.4.Final):

Topic Mojarra 4.1.17 MyFaces 4.1.4

primefaces.THEME names a theme that does not exist

writes <link rel="stylesheet" href="RES_NOT_FOUND" />

writes no theme link

Order of h:outputStylesheet resources declared in different facets and the head

body, last, first, middle

declaration order: body, first, middle, last

Client ids generated for components without an id

j_idt6, j_idt7

j_id_d-style ids; never rely on either, set the id

Theme link after a postback that re-renders the view

new and old link

new and old link (the same)

Hello-world, head order and theme switch on Faces 4.0

Mojarra 4.0.26 ran them unchanged

not run

Expression #{book.title} on a Book record (the Bookshelf catalogue page)

worked on 4.1.17; on Mojarra 4.0.26 with Tomcat 11.0.26 the page failed with a PropertyNotFoundException (cause not investigated; reading record components by their accessor names needs EL 6.0 with its RecordELResolver, see the expression language page)

worked on 4.1.4

Content Security Policy

has its own nonce setting, com.sun.faces.enableCspNonce

no CSP support of its own; primefaces.CSP works on both

What Changed per Version

PrimeFaces versions move fast and change defaults in every release, so a page or a book is only as good as its PrimeFaces version. The entries below are the ones that matter for this page, from the PrimeFaces migration guides and the 16.0.0 source:

Version Change

3.0 / 3.1

Aristo is the built-in theme (3.0); ThemeSwitcher is reimplemented as a native widget (3.0); primefaces.THEME_FORMS is removed (3.1)

5.0 / 6.0

Widgets are referenced through PF('widgetVar') (5.0). primefaces.js is split into core.js and components.js (6.0), and the last facet is supported only in h:head (6.0)

7.0

RequestContext is replaced by PrimeFaces.current(); Push and Mobile are removed

8.0

Java 8 or later; p:textEditor requires the OWASP sanitizer by default; p:panelGrid layout="flex" is added

10.0.0

aristo and omega themes removed and saga becomes the default; Grid CSS, Font Awesome and ThemeSwitcher are deprecated in favour of PrimeFlex, PrimeIcons and a normal menu

11.0.0

Font Awesome and ThemeSwitcher removed from the jar; PrimeFlex 3 (a breaking release) arrives

12.0.0

PrimeFlex 3 is the version to use

13.0.0

p:panelGrid defaults to layout="grid", the tabular layout is deprecated; Internet Explorer support is removed; Faces 2.0 and 2.1 support is deprecated

14.0.0

Java 11 is the minimum; Faces 2.0 to 2.2 support is removed; primefaces.CLIENT_SIDE_LOCALISATION is enabled by default

15.0.0

ThemeRoller themes are no longer supported; every theme except Saga Blue moves to primefaces-themes.jar; the themes are named saga-blue, arya-blue and vela-blue (the short names still work); Commons FileUpload support is removed

16.0.0

Java EE and javax.* support removed, so the baseline is Jakarta Faces 4.0 (Faces 5.0 is listed as supported by the download page); PrimeFlex 4.0.0 (its colour variables become --p-red-500); primefaces.HTML5_COMPLIANCE removed; PrimeClientWindowLifecycleFactory removed (use PrimeClientWindowFactory)

The Faces versions matter as well: the jakarta.faces.* namespaces of Faces 4.0 are the ones the PrimeFaces documentation uses for h: and f:, and a PrimeFaces 16 application cannot run on Faces 2.x or 3.x. The documentation tree of the v16.0.0 tag already holds a 17_0_0 folder, a development copy: no PrimeFaces 17 release exists at the time of writing and nothing on this page applies to it. See also the versions and implementations page of this section (Versions and Implementations).

Where the Books Are Outdated

PrimeFaces Cookbook, Second Edition (Çalışkan and Varaksin, Packt, 2015; PrimeFaces 5.1 and 5.2 on JSF 2.2) is the book behind this page: its first chapters cover setup, internationalisation and resource ordering, theming, icons, and panels and layouts. Its approach to organising a PrimeFaces application is still sound; the details are not.

  • The platform. The book is written for javax.faces on JSF 2.2; PrimeFaces 14.0.0 removed Faces 2.x support and 16.0.0 removed javax.* altogether. Every namespace, import and server setup needs the Jakarta Faces 4.0 or later form. The p: namespace http://primefaces.org/ui still works and primefaces is the short form.

  • Resource files. The 5.x scripts were collected in primefaces.js; since 6.0 they are core.js and components.js (plus jQuery), and in 16 the head renderer includes them, not an explicit tag or include. Nothing has to be added by hand; a custom @ResourceDependency that names primefaces.js has to change.

  • Themes. The book teaches ThemeRoller-style themes, the theme switcher component and Maven artifacts for extra themes. In PrimeFaces 16 ThemeRoller themes do not render (15.0.0), p:themeSwitcher does not exist (11.0.0), and the extra themes are Saga, Vela, Arya, Aura, Bootstrap, Material, Luna and Nova in the single primefaces-themes jar. The setting is still primefaces.THEME, but the names are different. Custom themes are built from SCSS or extended with CSS, as in A custom theme.

  • Icons. Font Awesome came with the jar in the book’s era; it was removed in 11.0.0. PrimeIcons (pi pi-*) is the default and Font Awesome is added as a WebJar with the -jsf style sheet (Icons: PrimeIcons and Font Awesome).

  • Grid CSS. The book’s layout recipes use the Grid CSS of its day (ui-grid, ui-grid-row, ui-grid-col-N). The classes still ship in 16.0.0, but Grid CSS was deprecated in 10.0.0 in favour of PrimeFlex, which is a separate WebJar with its own class names (grid, col-12 md:col-6), and p:panelGrid has the flex layout for it (Responsive Layout: ui-fluid, Grid CSS, PrimeFlex and panelGrid).

  • Resource ordering. The book’s advice on the order of style sheets and scripts rests on the head rendering of PrimeFaces 5. The facets first, middle and last of h:head still exist, but they order plain markup, not h:outputStylesheet components, and the order differs between Mojarra and MyFaces for the latter (The Head: What PrimeFaces Writes and in Which Order).

  • Internationalisation. The mechanism (bundles for the components, client-side locale scripts) is the same idea, but client-side localisation is on by default since 14.0.0 and the languages and keys are different (Localisation).

Other books touch this page too. Learning PrimeFaces Extensions Development (Jonna, 2014) sets up a PrimeFaces Extensions 1.2.1 project against PrimeFaces 4.0; the PrimeFaces Extensions page of this section covers the present setup. JSF 2.0 Cookbook (Leonard, 2010) was written for PrimeFaces 2.0 alongside RichFaces and Tomahawk; nothing of its PrimeFaces setup applies. None of the book content is reproduced here.

Common Mistakes

  • Plain <head> and <body> in a template. No theme, no icons, no widgets; the components are rendered as bare markup (The Head: What PrimeFaces Writes and in Which Order). Use h:head and h:body.

  • A typo in primefaces.THEME, or a theme name from primefaces-themes without the jar. An unstyled page and, on Mojarra, a RES_NOT_FOUND link; there is no error. Check the link in the page source after changing the theme (Choosing a theme).

  • Re-rendering the view after the user changes the theme. The old theme link is written as well, and it wins. Redirect to a fresh view (A theme per user).

  • Putting a user-chosen string into primefaces.THEME. It becomes a library name. Allow a fixed list.

  • Overriding PrimeFaces CSS with an h:outputStylesheet. It arrives before components.css. Use a plain link with #{resource[...]} in the last facet and select with more specificity than the theme (The Head: What PrimeFaces Writes and in Which Order).

  • Depending on facets to order h:outputStylesheet components. Facets order plain markup only, and the two implementations disagree about resource components (The Head: What PrimeFaces Writes and in Which Order).

  • Hello world without the sanitizer. The official p:textEditor page answers HTTP 500 until owasp-java-html-sanitizer is on the class path; the cure is the dependency, not secure="false".

  • Font Awesome with the plain style sheet, or with the WebJar 7.3.0. The icons render as empty boxes because the font is not found. Use the *-jsf.css file and check that the version you choose still has it (Icons: PrimeIcons and Font Awesome).

  • Trusting the documentation’s configuration table for CLIENT_SIDE_LOCALISATION. The table says false; the default is true (The Context Parameters).

  • Relying on saga, arya and vela short names. They still work in 16.0.0 but were announced for removal; use the -blue names.

  • Mixing PrimeFlex and Grid CSS in one form without meaning to. p:panelGrid defaults to Grid CSS even when primefaces.FLEX is true; the attribute layout="flex" and the PrimeFlex style sheet go together.

  • Reading PrimeFaces.settings as if it were configuration. It is generated on every page from the context parameters; change web.xml.

  • A WELD-000119 flood taken for a failure. The INFO lines on a servlet container without Bean Validation or Persistence (What PrimeFaces Adds to Faces) mean that optional features are off.

  • Different PrimeFaces and primefaces-themes versions. They are released together; keep the same number in the POM.

  • Hard-coded keys. No captcha key, API key or private value in web.xml, a bean or a page (Security Settings of the Setup).