Tomcat and Weld: Faces on a Servlet Container
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2, part of Jakarta EE 11), Eclipse Mojarra 4.1.17 and Apache MyFaces
Core 4.1.4, Apache Tomcat 11.0.26, Weld weld-servlet-shaded 6.0.4.Final, Hibernate Validator 9.1.4.Final, Eclipse Parsson 1.1.9,
Jandex 3.6.0 and Eclipse Jetty 12.1.14, all checked on 2026-10-08. The application targets Java 17 (Faces 4.1, Tomcat 11 and Weld 6 all
need Java 17 or later); the examples were compiled with --release 17 and run on JDK 26. Faces 4.0 (Jakarta EE 10, Tomcat 10.1, Weld 5)
works the same way unless the text says otherwise, and Faces 5.0 (Jakarta EE 12) is still a milestone (jakarta.faces-api 5.0.0-M9) and is
mentioned only as upcoming.
The examples on this page were built and run: the Bookshelf WAR below was built with Maven once for Mojarra and once for MyFaces, deployed
to a standalone Tomcat 11.0.26, to Jetty 12.1.14 and to embedded Tomcat, and driven with curl, a small Java WebSocket client and a
second set of throw-away variants (no beans.xml, no Weld, other Weld releases, with and without the BeanManager binding and so on).
Where something was read from the documentation or from decompiled class files and not run, the text says so.
A Jakarta EE application server ships Faces, CDI, Bean Validation and the rest, so a Faces WAR for it contains no Faces JARs (see Jakarta EE Servers). A Servlet container such as Tomcat ships only the web tier. Faces needs more than that: a CDI container, because since Faces 4.0 the managed-bean facility is gone and every bean, scope and injection point is CDI; a Faces implementation; and, when you use them, Bean Validation and JSON Processing. On Tomcat you bring those yourself, inside the WAR, and you choose the implementation. That freedom is the reason to choose Tomcat, and the price is the wiring described here.
What Tomcat Provides and What You Add
Tomcat 11.0.x implements Servlet 6.1, Pages (JSP) 4.0, Expression Language 6.0, WebSocket 2.2, Authentication 3.1 and Annotations 3.0, and requires Java 17 or later. It does not implement CDI, Faces, Bean Validation, JSON Processing or the Standard Tag Library.
| Need | Provided by | What the WAR adds |
|---|---|---|
Servlet 6.1, WebSocket 2.2, Annotations 3.0 |
Tomcat ( |
Nothing. Compile against them with |
Expression Language 6.0 |
Tomcat ( |
Nothing. Faces, Hibernate Validator and the Facelets tags all use Tomcat’s EL (checked: a |
CDI 4.1 |
Not provided |
|
Faces 4.1 |
Not provided |
Mojarra: |
Bean Validation 3.1 (optional) |
Not provided |
|
JSON Processing 2.1 (only Mojarra with |
Not provided |
|
Standard Tag Library ( |
Not provided |
Nothing: the Facelets tags of both implementations ran without a JSTL JAR. |
Source: Apache Tomcat: which version, Mojarra 4.1 README: minimum requirements and installation and Apache MyFaces Core 4.1: requirements
The Mojarra README lists Java 17, Servlet 6.1, EL 6.0 and CDI 4.1 as the minimum, and WebSocket 2.2, JSON Processing 2.1 and Validation 3.1
as optional extras that matter only for f:websocket and f:validateBean; the MyFaces page lists Java 17, Servlet 6.1, EL 6.0 and
CDI 4.1 as requirements and JSTL 3.0 and Bean Validation 3.1 as optional. (Jetty 12 shows that "optional" does not hold for the WebSocket
API on a container that does not ship it.) The diagram shows which side each piece comes from.
Tomcat’s web application class loader looks in the WAR before it delegates, except for the Jakarta EE APIs that Tomcat implements itself
(Servlet, JSP, EL and WebSocket), where it always delegates first. That is why a JAR with jakarta.servlet.* classes in WEB-INF/lib
does not replace Tomcat’s own, and why it is pointless. In the run below, jakarta.annotation.PostConstruct was loaded from Tomcat’s
annotations-api.jar even when the WAR also carried jakarta.annotation-api-3.0.0.jar; the Maven build here keeps that JAR out of the
WAR.
Which Combinations Work
The matrix is what happened on Tomcat 11.0.26 with the Bookshelf pages (catalogue, a session-scoped counter, a form post). Only "Mojarra 4.1.17 or MyFaces 4.1.4 with Weld 6.0.4.Final" is a combination to ship; the others are here because the failures are instructive.
| Faces | CDI container | Result |
|---|---|---|
Mojarra 4.1.17 |
Weld 6.0.4.Final (CDI 4.1) |
Works. This is the recommended pair. |
MyFaces 4.1.4 |
Weld 6.0.4.Final |
Works. |
Mojarra 4.0.26 |
Weld 6.0.4.Final or Weld 5.1.7.Final (CDI 4.0) |
Works. |
MyFaces 4.1.4 |
Weld 5.1.7.Final |
Works in a quick check, although the MyFaces 4.1 page lists CDI 4.1 as the requirement. |
Mojarra 4.1.17 |
Weld 5.1.7.Final |
The context fails to start: |
Mojarra 4.1.17 |
Weld 7.0.0.Final (CDI 5, the Jakarta EE 12 line) |
Started and served the quick-check pages. Unsupported with Faces 4.1; do not ship it. |
MyFaces 4.1.4 |
Weld 7.0.0.Final |
The first request fails with |
Mojarra 4.1.17 |
no CDI container |
The context fails to start with |
MyFaces 4.1.4 |
no CDI container |
The context fails to start: |
The Weld reference guide says Tomcat 10.1 is the release Weld Servlet is tested with; the runs here on Tomcat 11.0.26 with Weld 6.0.4.Final
worked for both implementations, and the Mojarra README names weld-servlet-shaded 6.0.x for Servlet containers. Weld 7.0.0.Final exists
as the CDI 5 line for Jakarta EE 12; stay on 6.0.x with Faces 4.1.
The Maven Build
The WAR has three kinds of dependency: APIs that Tomcat already provides (compile-time only), the pieces you must ship, and optional
add-ons. The same pom.xml builds the Mojarra WAR by default and the MyFaces WAR with mvn -P myfaces package, because the two
implementations are profiles.
<?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</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
<mojarra.version>4.1.17</mojarra.version>
<myfaces.version>4.1.4</myfaces.version>
<weld.version>6.0.4.Final</weld.version>
<hibernate-validator.version>9.1.4.Final</hibernate-validator.version>
<parsson.version>1.1.9</parsson.version>
<jandex.version>3.6.0</jandex.version>
</properties>
<dependencies>
<!-- Compile-time APIs only. Tomcat supplies Servlet and EL; Weld (below) supplies the CDI API. -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
<!-- Tomcat 11 ships Jakarta Annotations 3.0; keep the copies pulled in by Weld and Mojarra out of the WAR -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<!-- CDI container: the shaded artifact carries the CDI API, Weld core and the servlet integration -->
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet-shaded</artifactId>
<version>${weld.version}</version>
</dependency>
<!-- Optional: lets Weld scan bean archives faster than with reflection -->
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>jandex</artifactId>
<version>${jandex.version}</version>
</dependency>
<!-- Optional: Bean Validation provider (needed for constraints on Faces input) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!-- Optional: Jakarta JSON Processing, needed by Mojarra only when f:websocket is used -->
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
<version>${parsson.version}</version>
</dependency>
</dependencies>
<build>
<finalName>bookshelf</finalName>
</build>
<profiles>
<profile>
<id>mojarra</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.faces</artifactId>
<version>${mojarra.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>myfaces</id>
<dependencies>
<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>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Source: Mojarra 4.1 README: Maven coordinates for Servlet containers, Apache MyFaces Core 4.1: dependency and Weld 6.0.4.Final reference: weld-servlet-shaded
What each block does:
-
jakarta.jakartaee-web-apiwithprovidedscope is only for compiling. It contains the Faces, CDI, Servlet, EL, Validation and WebSocket APIs, so nothing from it reaches the WAR. The run-time classes of Servlet, EL and WebSocket come from Tomcat and those of the rest from the JARs below. Do not usecompilescope for it: a second copy ofjakarta.faces.*orjakarta.servlet.*inWEB-INF/libis the classic source of class-loading errors. -
jakarta.annotation-apiis declaredprovidedon purpose. Weld and Mojarra both pull version 3.0.0 in as a compile dependency, Tomcat 11 already contains Annotations 3.0, and a declaration at the top level wins over the transitive ones, so the JAR stays out of the WAR. Removing it by hand fromWEB-INF/libof an otherwise identical WAR also worked on both implementations. -
weld-servlet-shadedis the single-JAR variant of Weld Servlet. The Weld guide also documentsweld-servlet-core, which fetches the CDI API and Weld core transitively; I did not run that variant. -
hibernate-validatorandparssonare the optional add-ons (see Bean Validation on Tomcat and JSON Processing and f:websocket).jandexis optional too: with it on the class path Weld logsWELD-ENV-000020: Using jandex for bean discoveryinstead of falling back to Java reflection (WELD-ENV-000014). Both worked; Jandex only speeds up the scan. -
The Faces implementation is the only part that differs between the profiles. Mojarra is one JAR that holds the API and the implementation. MyFaces is two JARs,
myfaces-apiandmyfaces-impl.
The Mojarra WAR contains nine JARs (Faces, Weld, Jandex, Hibernate Validator with its three dependencies, Parsson and the JSON API) and the MyFaces WAR ten, which is the whole list: no Servlet, EL, annotation or CDI-API JAR next to Weld.
The Deployment Descriptors
Three small files complete the WAR. web.xml is optional for Faces (Faces registers FacesServlet itself on a Servlet container), but
every real application sets the project stage and an explicit mapping.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
version="6.1">
<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>
<context-param>
<param-name>jakarta.faces.PROJECT_STAGE</param-name>
<param-value>Production</param-value>
</context-param>
<context-param>
<param-name>jakarta.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name>
<param-value>true</param-value>
</context-param>
<welcome-file-list>
<welcome-file>catalogue.xhtml</welcome-file>
</welcome-file-list>
<!-- Declares the BeanManager reference that META-INF/context.xml defines (see the BeanManager section) -->
<resource-env-ref>
<resource-env-ref-name>BeanManager</resource-env-ref-name>
<resource-env-ref-type>jakarta.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
</web-app>
-
The
facesServletmapping to*.xhtmlis the one the Mojarra README recommends. Without it Faces still registers itself on*.xhtml,*.jsf,*.facesand/faces/*, so the same view answers on four URLs (checked on both implementations with the servlet and its mapping removed fromweb.xml). An explicit mapping replaces the implicit ones. Project Setup explains the mappings. -
jakarta.faces.PROJECT_STAGEisProduction(see Configuration and Project Stages). -
jakarta.faces.ENABLE_WEBSOCKET_ENDPOINTis needed only forf:websocket(JSON Processing and f:websocket); without it Mojarra fails when it renders the tag and MyFaces renders it but the handshake fails. -
The
resource-env-refbelongs to the BeanManager binding and is explained in The BeanManager Binding in META-INF/context.xml. It is the one line of this file that is specific to Tomcat: Jetty refuses to deploy the WAR with it (Jetty 12).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd"
version="4.1" bean-discovery-mode="annotated"/>
The file is not optional on Tomcat; beans.xml and Bean Discovery shows what happens without it. faces-config.xml is optional as well; the Bookshelf
one only registers the resource bundle under the variable msg:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_1.xsd"
version="4.1">
<application>
<resource-bundle>
<base-name>org.example.bookshelf.messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
The Java classes of the Bookshelf WAR are the ones used elsewhere in this section (the Book record, the application-scoped
BookService, the request-scoped CatalogueBean, the layout template and the msg bundle) plus RuntimeInfoBean from
the Jakarta EE servers page, so the identical sources run on Tomcat and on the servers. Three classes are specific to this page and appear
below: NoteBean (validation), AnnounceServlet (WebSocket) and BeanManagerInfoServlet (the BeanManager diagnostics).
How Weld Hooks Into Tomcat
Weld’s Servlet module needs no declaration in web.xml. weld-servlet-shaded registers a ServletContainerInitializer
(org.jboss.weld.environment.servlet.EnhancedListener), Tomcat calls it before the application starts, and it scans the bean archives,
starts the CDI container and makes the BeanManager available. The log of the Mojarra run shows the sequence:
INFO [main] EnhancedListener.onStartup WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
INFO [main] WeldStartup.<clinit> WELD-000900: 6.0.4 (Final)
INFO [main] DiscoveryStrategyFactory.create WELD-ENV-000020: Using jandex for bean discovery
INFO [main] WeldStartup.startContainer WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
INFO [main] TomcatContainer.initialize WELD-ENV-001100: Tomcat 7+ detected, CDI injection will be available in Servlets, Filters and Listeners.
INFO [main] ConfigureListener.contextInitialized Initializing Mojarra 4.1.17 for context '/bookshelf'
INFO [main] Version.<clinit> HV000001: Hibernate Validator 9.1.4.Final
The MyFaces run prints the same Weld lines, then MyFacesContainerInitializer.onStartup, Reading config /WEB-INF/faces-config.xml,
MyFaces Core Bean Validation support enabled and MyFaces Core has started. Weld starts first in both runs, which matters: both Faces
implementations ask the CDI container for the BeanManager while they initialize (The BeanManager Binding in META-INF/context.xml).
What the log lines mean:
-
WELD-ENV-001100is the Tomcat integration (org.jboss.weld.environment.tomcat.TomcatContainer). It makes Tomcat create servlets, filters and listeners through Weld, so@Injectworks in them. TheAnnounceServletbelow depends on it, and so does a@WebListenerthat injects a bean. Weld detects the container by itself; the guide documents the servlet initialization parameterorg.jboss.weld.environment.container.classfor the rare case where detection fails, withTomcatContainer,JettyContainerandUndertowContaineras values (not run). -
WELD-000101is information, not a problem. Weld Servlet has no JTA integration: the guide states that session beans,@EJB,@PersistenceContextand transactional events are not supported in a Servlet container.@Transactionaland EJBs are therefore a reason to use an application server or a transaction manager that you configure yourself (see Persistence and Services). -
You do not need to register
org.jboss.weld.environment.servlet.Listenerinweb.xmlon Tomcat. Adding it was harmless (the log gainsWELD-ENV-001006andWELD-ENV-001009, "used for ServletRequest and HttpSession notifications") and both implementations ran the same. The Weld guide describes the manual listener for containers withoutServletContainerInitializersupport and for control over the listener order, and describesorg.jboss.weld.module.web.servlet.WeldTerminalListeneras the last listener for code that needs the session context inHttpSessionListener.sessionDestroyed(); neither was needed here and the second was not run. -
org.jboss.weld.environment.servlet.archive.isolationis a servlet initialization parameter (set it as acontext-param). Bean archive isolation is on by default, so alternatives, interceptors and decorators enabled in one archive’sbeans.xmlapply to that archive only;falseflattens the deployment so allbeans.xmlfiles merge into one (documented in the guide; not run). -
WELD_CONTEXT_ID_KEYis a context parameter that gives each deployment its own Weld container (useful for several deployments in one JVM, for example with the Arquillian Tomcat container, per the guide; not run).
beans.xml and Bean Discovery
CDI 4 changed the default bean discovery mode to annotated: only classes with a bean-defining annotation (a scope such as
@RequestScoped, a stereotype, @Dependent and so on) are beans, and a class that has only @Named is not. The Bookshelf WAR has
a TipBean with only @Named, a request-scoped NoteBean and a session-scoped VisitBean for the experiment below
(visits.xhtml prints #{visitBean.visits} and #{tipBean.text}). The table is what each WEB-INF/beans.xml variant produced on
Tomcat; the results were identical for Mojarra and MyFaces unless a row says otherwise.
WEB-INF/beans.xml |
Result |
|---|---|
|
The scoped beans are found; |
|
Every class is a bean, so |
|
Same as |
a zero-byte file |
Same as |
no file at all |
Nothing in the WAR is discovered, although the CDI specification allows an implicit bean archive without the file. The Weld guide says
Weld Servlet does not support such an archive and excludes it from discovery. Value expressions render empty and the form post fails
with HTTP 500 and |
Source: Jakarta CDI 4.1 specification, 2.5 Default bean discovery mode and Weld 6.0.4.Final reference: Implicit Bean Archive Support
The rule for Tomcat is therefore: ship WEB-INF/beans.xml, keep annotated, and give every class that EL or injection must reach a
bean-defining annotation. The same applies to application JARs in WEB-INF/lib: each needs its own META-INF/beans.xml to be
scanned. Both Faces implementations are CDI portable extensions (they carry META-INF/services/jakarta.enterprise.inject.spi.Extension,
Mojarra five entries and MyFaces twelve), so they need no beans.xml from you. The backing beans page and the project setup page of this
section explain discovery for beans in general, and the events page notes that @Inject into a phase listener registered in
faces-config.xml worked on MyFaces and not on Mojarra in this setup.
The BeanManager Binding in META-INF/context.xml
Faces talks to CDI constantly: the Expression Language resolver that finds #{noteBean}, the producers behind implicit objects such as
#{requestScope}, the view, flow and client-window scopes, injection into converters, validators and listeners, and f:websocket.
All of it goes through the CDI BeanManager. In CDI 4.1 an application gets it by injection (@Inject BeanManager) or
from CDI.current(); the specification describes no JNDI lookup. On a Jakarta EE server the server additionally binds the manager in
JNDI, and both Faces implementations look there first, under java:comp/BeanManager.
Tomcat’s JNDI is a read-only naming context that only Tomcat itself fills, and Tomcat only allows entries under java:comp/env. Weld
therefore cannot bind the BeanManager on its own. The Weld guide’s answer is a Tomcat Resource in META-INF/context.xml that
names a Weld object factory, plus a resource-env-ref in web.xml. This section explains what the binding is, what it changes, how
the two implementations behave with and without it, and what breaks when it is wrong. The first thing to know is the result: on
Tomcat 11.0.26 with Weld 6.0.4.Final, neither Mojarra 4.1.17 nor MyFaces 4.1.4 needed the binding to run the Bookshelf WAR, because both fall back to
CDI.current(). The binding is optional; it is worth having when you want the real manager in JNDI for other code.
How the Two Implementations Find the BeanManager
The order below was read from the class files (javap -c on com.sun.faces.util.Util.getCdiBeanManager(FacesContext) in
Mojarra 4.1.17 and on org.apache.myfaces.webapp.FacesInitializerImpl.initCDIIntegration() in MyFaces 4.1.4) and confirmed by the
diagnostics below. It is an implementation detail and may change between releases; neither project documents it.
Source: Apache MyFaces source and Eclipse Mojarra source (the lookup order was read from the class files of the versions named above).
| Mojarra 4.1.17 | MyFaces 4.1.4 | |
|---|---|---|
When |
On demand, the first time a Faces artefact needs it; the result is cached in the application map under |
Once at start-up ( |
JNDI names tried |
|
|
Fallback |
|
|
No manager found |
|
The CDI integration is not installed; with Weld skipped the start-up error shown in beans.xml and Bean Discovery follows. |
On Tomcat the first JNDI name never works (Tomcat binds nothing outside java:comp/env), so with the binding in place the second lookup
succeeds, and without it both fail with a NamingException that the implementations swallow before they use CDI.current().
Piece 1: The Resource Element
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- Binds Weld's BeanManager at java:comp/env/BeanManager -->
<Resource name="BeanManager"
auth="Container"
type="jakarta.enterprise.inject.spi.BeanManager"
factory="org.jboss.weld.resources.ManagerObjectFactory"/>
</Context>
Source: Weld 6.0.4.Final reference: Binding BeanManager to JNDI and Apache Tomcat 11: The Context Container, Resource Definitions
The Context element is Tomcat’s per-web-application configuration, and a Resource inside it declares what a JNDI lookup under
java:comp/env returns.
-
name="BeanManager"is the name relative tojava:comp/env, so the entry isjava:comp/env/BeanManager. -
typeis the Java type that a lookup is expected to produce,jakarta.enterprise.inject.spi.BeanManager. -
factorynames the object factory,org.jboss.weld.resources.ManagerObjectFactoryfrom Weld Servlet. It implementsjavax.naming.spi.ObjectFactory: Tomcat calls it to produce the object behind the name, and it returns Weld’sBeanManagerfor the web application. Tomcat’s general advice for custom factories is to put them under$CATALINA_HOME/lib; here the factory sits inWEB-INF/libof the WAR (insideweld-servlet-shaded) and was found there, so no change to the Tomcat installation is needed. -
auth="Container"is what the Weld guide shows. The Tomcat documentation says the attribute is required when the application declares aresource-refand optional with aresource-env-ref; the lookup worked the same with the attribute left out.
Piece 2: The resource-env-ref in web.xml
The matching entry in web.xml (already part of the descriptor above):
<resource-env-ref>
<resource-env-ref-name>BeanManager</resource-env-ref-name>
<resource-env-ref-type>jakarta.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
A resource-env-ref is the Jakarta EE way to declare that the application uses a resource by a logical name. Tomcat links it to the
Resource of the same name. On Tomcat 11.0.26 the Resource alone was enough for the lookup to succeed, so the web.xml
entry documents the dependency rather than creating it; the Weld guide asks for both and this page keeps both. The reverse is not true: the
web.xml entry without the Resource creates nothing (see the failure table below).
Piece 3: Where the Context Fragment Lives
META-INF/context.xml in the WAR is only one of the places Tomcat reads a Context from. Per the Tomcat documentation:
-
/META-INF/context.xmlinside the web application (used above). If the host’scopyXMLattribute istrue, Tomcat copies it to$CATALINA_BASE/conf/<engine>/<host>/<name>.xmland uses the copy on later starts. -
$CATALINA_BASE/conf/<engine>/<host>/<name>.xml(for exampleconf/Catalina/localhost/bookshelf.xml). This file takes precedence over the one in the WAR. Putting theResourcethere worked with a plainbookshelf.warthat contained nocontext.xml. -
A
Contextinside theHostofserver.xml, which Tomcat discourages, or the sharedconf/context.xml, which would apply the entry to every web application.
Security-conscious installations set deployXML="false" on the Host, so that applications cannot bring their own container
configuration. With that setting, Tomcat 11.0.26 refused to deploy the Bookshelf WAR at all and logged The web application with context path
[/bookshelf] was not deployed because it contained a deployment descriptor [META-INF/context.xml] which may include configuration necessary for
the secure deployment of the application but processing of deployment descriptors is prevented by the deployXML setting of this host. An appropriate
descriptor should be created at [...]/conf/Catalina/localhost/bookshelf.xml to deploy this application. In that setup the administrator provides the
file under conf/Catalina/localhost, as in the example below, and the WAR carries no context.xml.
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="BeanManager"
auth="Container"
type="jakarta.enterprise.inject.spi.BeanManager"
factory="org.jboss.weld.resources.ManagerObjectFactory"/>
</Context>
What the Binding Changes
The diagnostic servlet prints every place the manager can be found and which instance each Faces implementation cached.
package org.example.bookshelf.web;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.servlet.ServletContext;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/** Diagnostic only: shows where the BeanManager can be found. Do not deploy it to production. */
@WebServlet("/diagnostics/beanmanager")
public class BeanManagerInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
PrintWriter out = resp.getWriter();
ServletContext ctx = getServletContext();
out.println("CDI.current() = " + CDI.current().getBeanManager().getClass().getName());
out.println("JNDI java:comp/BeanManager = " + lookup("java:comp/BeanManager"));
out.println("JNDI java:comp/env/BeanManager = " + lookup("java:comp/env/BeanManager"));
// Mojarra caches its choice under this key, MyFaces under the second one
out.println("Mojarra cache = " + name(ctx.getAttribute("com.sun.faces.cdi.BeanManager")));
out.println("MyFaces cache = " + name(ctx.getAttribute("oam.cdi.BEAN_MANAGER_INSTANCE")));
out.println("Weld attribute = "
+ name(ctx.getAttribute("org.jboss.weld.environment.servlet.jakarta.enterprise.inject.spi.BeanManager")));
}
private static String lookup(String jndiName) {
try {
return name(new InitialContext().lookup(jndiName));
} catch (NamingException e) {
return e.getClass().getSimpleName() + ": " + e.getMessage();
}
}
private static String name(Object value) {
return value == null ? "null" : value.getClass().getName();
}
}
Its output on Tomcat 11.0.26 for the four ways of deploying the Mojarra WAR (full is the descriptor and context.xml above; the
MyFaces WAR printed the same with the MyFaces cache line in place of the Mojarra cache line):
| Deployment | java:comp/env/BeanManager |
Instance cached by Mojarra | Page behaviour |
|---|---|---|---|
|
|
|
Works. |
|
|
|
Works. |
|
|
|
Works; the lookup failed and |
neither |
|
|
Works; same fallback. |
java:comp/BeanManager printed NameNotFoundException in all four cases, and CDI.current().getBeanManager() always returned the
BeanManagerProxy. The Weld servlet context attribute held the BeanManagerImpl. The binding therefore changes one thing in Faces: the
implementation caches the real manager instead of Weld’s proxy. A wrong factory class name gave NamingException: Could not load resource
factory class for the lookup and the page still worked through the fallback. In every row the catalogue, the validation form and the
session counter behaved identically on both implementations.
Why keep it, then?
-
Other code looks the manager up in JNDI. OmniFaces
Beans.getManager()triesCDI.current()and, if that fails, falls back tojava:comp/BeanManagerandjava:comp/env/BeanManager(read from the 5.5.5 class file). Portable libraries and older code written for application servers do the same. Without the binding such a fallback finds nothing. -
A real
BeanManagerImplis cached, not a proxy that must be resolved through the thread’s context class loader on each use. I did not find a failure that this prevents on Tomcat 11.0.26; it is a robustness argument, not a demonstrated fix. -
The binding is what the Weld guide documents as the supported way to expose the manager on Tomcat.
If nothing in your application or its libraries needs JNDI, leaving the binding out is a legitimate choice, and it removes the Tomcat-only lines that make the WAR non-portable.
Where the Binding Does Not Apply
-
Jetty has no
context.xmland refuses aresource-env-refthat nothing binds: the Bookshelf WAR with the entry failed to start withIllegalStateException: Nothing to bind for name BeanManager(HTTP 503). Remove the entry from the WAR you deploy to Jetty. The Jetty documentation says such names must be bound in Jetty XML (not run). With the entry removed both implementations usedCDI.current()(Jetty 12). -
Embedded Tomcat has JNDI switched off. Without
tomcat.enableNaming()both JNDI lookups failed withNoInitialContextExceptionand the implementations usedCDI.current(); withenableNaming()theMETA-INF/context.xmlof the exploded WAR was honoured andjava:comp/env/BeanManagerreturned theBeanManagerImpl(Embedded Tomcat). -
Jakarta EE servers bind
java:comp/BeanManagerthemselves. Thecontext.xmlis ignored there (WildFly, GlassFish, Payara, Open Liberty and TomEE were not run with this WAR). Aresource-env-reffor a name the server does not provide may be a deployment problem, so keep the Tomcat-specific lines in a Tomcat-specific profile or descriptor when one WAR targets several runtimes.
Shutdown
Stopping the web application showed a difference between the implementations that is independent of the binding. Mojarra stopped without a
message. MyFaces 4.1.4 logged a SEVERE entry from StartupServletContextListener while destroying itself, because Weld had already shut
down: WELD-001333: BeanManager method getBeans() is not available after shutdown with the proxy, and WELD-001456: Argument bean must not
be null with the JNDI-bound manager. Tomcat then added checkThreadLocalMapForLeaks entries mentioning StartupFacesContextImpl. The
application kept working until it was stopped, and I found no effect other than the log noise, but expect those lines on every redeploy of a
MyFaces application on Tomcat 11.0.26 with Weld 6.0.4 and do not read them as a failure of your own code.
Bean Validation on Tomcat
Bean Validation is optional on Tomcat. Faces calls it for any input component whose value expression points at a bean property with
constraints (see Bean Validation for the lifecycle and f:validateBean).
-
No provider on the class path. The constraints are silently not enforced. A 250-character note and a note that was a single space (which
@NotBlankrejects) were both saved by the Bookshelf form on both implementations, also when only the Validation API JAR was left out; MyFaces loggedMyFaces Core Bean Validation support disabledat start-up, Mojarra said nothing. A form that relies on@NotBlanktherefore accepts blanks until a provider is added. This is the failure to watch for: it produces no error. -
Hibernate Validator on the class path (the dependency in the
pom.xmlabove). Mojarra and MyFaces both pick it up, with no configuration, through the Validation API’s service lookup; MyFaces logsBean Validation support enabled. The messages are interpolated with Tomcat’s EL implementation, so${validatedValue}in a message works without an extra EL JAR. Never build a constraint message from user input: the template is evaluated as an expression. -
Constraint validators that use
@Inject. On Tomcat the@Injectfield staysnull: the form fails with HTTP 500 andValidationException: HV000028: Unexpected exception during isValid callcaused by aNullPointerException, with Hibernate Validator alone and withhibernate-validator-cdiadded. Faces builds its own defaultValidatorFactory, which knows nothing about the bean manager. The fix, which was run on both implementations, is to inject the CDI-aware factory from a listener and give it to Faces underBeanValidator.VALIDATOR_FACTORY_KEY; the Bean Validation page shows the listener (the injection section and theValidatorFactorysection), so it is not repeated here. The listener needshibernate-validator-cdi, and without that JAR the context fails to start because there is nothing to inject.
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
/** A short reader note: Bean Validation constraints are checked by Faces in Process Validations. */
@Named
@RequestScoped
public class NoteBean {
@NotBlank
@Size(max = 200)
private String text;
private String saved;
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getSaved() { return saved; }
public void save() {
saved = "Saved " + text.length() + " characters.";
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets">
<ui:composition template="/WEB-INF/templates/layout.xhtml">
<ui:define name="title">Reader note</ui:define>
<ui:define name="content">
<h:form id="noteForm">
<h:messages id="msgs"/>
<h:inputTextarea id="text" value="#{noteBean.text}"/>
<h:commandButton id="save" value="Save" action="#{noteBean.save}"/>
<p><h:outputText id="saved" value="#{noteBean.saved}"/></p>
</h:form>
</ui:define>
</ui:composition>
</html>
Source: Jakarta EE Tutorial: Configuring Jakarta Faces Applications and Jakarta Validation 3.1 specification
A valid note answers Saved 11 characters.; a blank one must not be blank and a 250-character one size must be between 0 and 200. The
message text is the same on both implementations. The only difference in the page is the presentation of h:messages: MyFaces prefixes the client id
(noteForm:text: must not be blank) and Mojarra shows the bare summary.
JSON Processing and f:websocket
f:websocket and @Push need three things on Tomcat: the jakarta.faces.ENABLE_WEBSOCKET_ENDPOINT context parameter (in the
web.xml above), the WebSocket API and implementation (Tomcat has both), and, for Mojarra, a JSON Processing implementation. Tomcat has
no JSON-P. The Bookshelf "new arrivals" page and the servlet that pushes to it:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>New arrivals</title>
<script>
function onArrival(message) {
document.getElementById('status').textContent = 'Update: ' + message;
}
</script>
</h:head>
<h:body>
<f:websocket channel="arrivals" scope="application" onmessage="onArrival"/>
<p id="status">Waiting for new arrivals.</p>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.faces.push.Push;
import jakarta.faces.push.PushContext;
import jakarta.inject.Inject;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/** Tells every open catalogue page that the shelf changed. Protect it with a security constraint in a real application. */
@WebServlet("/arrivals/announce")
public class AnnounceServlet extends HttpServlet {
@Inject
@Push(channel = "arrivals")
private PushContext arrivals;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
arrivals.send("catalogue-changed");
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
The WebSocket Push page covers the tag, scopes, security and clustering. Observed on Tomcat 11.0.26, with a Java client that loads the page, opens the socket and triggers the servlet:
import java.net.CookieManager;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Opens the page's WebSocket, triggers the push and prints the raw frame: java ArrivalsClient.java http://localhost:8080/bookshelf */
public class ArrivalsClient {
public static void main(String[] args) throws Exception {
String base = args[0];
HttpClient client = HttpClient.newBuilder().cookieHandler(new CookieManager()).build();
String html = client.send(HttpRequest.newBuilder(URI.create(base + "/arrivals.xhtml")).build(),
HttpResponse.BodyHandlers.ofString()).body();
// The f:websocket tag renders a call such as faces.push.init('id','/bookshelf/jakarta.faces.push/arrivals?token', ...)
Matcher m = Pattern.compile("faces\\.push\\.init\\('[^']*','([^']*)'").matcher(html);
if (!m.find()) {
System.out.println("no faces.push.init in the page: is ENABLE_WEBSOCKET_ENDPOINT set?");
return;
}
String path = m.group(1);
URI socket = URI.create(path.startsWith("/")
? "ws://" + URI.create(base).getAuthority() + path : path);
CountDownLatch received = new CountDownLatch(1);
WebSocket ws = client.newWebSocketBuilder().buildAsync(socket, new WebSocket.Listener() {
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("open");
webSocket.request(1);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("raw frame: " + data);
received.countDown();
return null;
}
}).get(5, TimeUnit.SECONDS);
Thread.sleep(500);
int status = client.send(HttpRequest.newBuilder(URI.create(base + "/arrivals/announce"))
.POST(HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()).statusCode();
System.out.println("announce status " + status);
System.out.println(received.await(5, TimeUnit.SECONDS) ? "received" : "timeout");
ws.sendClose(WebSocket.NORMAL_CLOSURE, "done");
}
}
| WAR | Raw frame received | Notes |
|---|---|---|
Mojarra with Parsson |
|
Works. The |
MyFaces with Parsson |
|
Works. A bare JSON string; the |
Mojarra without Parsson |
nothing |
The servlet answers 500 with |
MyFaces without Parsson |
|
Works without any JSON-P JAR in this run (the WebSocket Push page did not test it). |
Add parsson (the Eclipse JSON-P reference implementation; it brings jakarta.json-api) to every Tomcat WAR that uses f:websocket with Mojarra, and
to the MyFaces WAR too if you want the same classpath on both. The Mojarra README names org.glassfish:jakarta.json 2.1.x for this role;
Parsson 1.1.9 is its successor and worked. The two frame shapes differ, and each implementation’s own client script (read from the 4.1.17 and 4.1.4 JARs) expects its own, so use the
script that Faces renders for f:websocket and do not hand-write WebSocket client code against the channel.
Expression Language, JSTL and JSP
Tomcat’s EL 6.0 is the EL for Faces and for Hibernate Validator; do not package an EL JAR (org.glassfish.expressly or jakarta.el-api) in the WAR.
Facelets does not use JSP, but Tomcat still has the JSP servlet, which matters only for embedded Tomcat (Embedded Tomcat).
The Facelets tag libraries jakarta.tags.core (c:) and jakarta.tags.functions (fn:) are implemented by Mojarra and MyFaces themselves.
A page that used c:if, c:forEach and fn:toUpperCase rendered the same on both implementations with no JSTL JAR in the WAR and none in
Tomcat. Build Time versus Render Time explains why c: tags are not interchangeable with ui: and h: ones.
In Faces 4.x the namespaces are jakarta.tags.core and jakarta.tags.functions.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:c="jakarta.tags.core"
xmlns:fn="jakarta.tags.functions">
<h:head><title>JSTL check</title></h:head>
<h:body>
<p id="out">c:if = <c:if test="#{true}">yes</c:if>,
c:forEach = <c:forEach items="#{['a', 'b']}" var="x">#{x}</c:forEach>,
fn:toUpperCase = #{fn:toUpperCase('abc')}</p>
</h:body>
</html>
Source: Jakarta EE Tutorial: Facelets
The output on Mojarra and on MyFaces was c:if = yes, c:forEach = ab, fn:toUpperCase = ABC.
Running the WAR on a Standalone Tomcat
Build both WARs, then deploy one of them. The steps below keep the Tomcat installation (CATALINA_HOME) separate from the instance
configuration and deployments (CATALINA_BASE), which is how Tomcat is meant to be run and what these tests used (on other ports than
the default 8080).
mvn clean package # Mojarra: target/bookshelf.war
mvn clean package -P myfaces # MyFaces: target/bookshelf.war (overwrites the first)
curl -fLO https://dlcdn.apache.org/tomcat/tomcat-11/v11.0.26/bin/apache-tomcat-11.0.26.tar.gz
curl -fLO https://dlcdn.apache.org/tomcat/tomcat-11/v11.0.26/bin/apache-tomcat-11.0.26.tar.gz.sha512
shasum -a 512 -c apache-tomcat-11.0.26.tar.gz.sha512 # must print: apache-tomcat-11.0.26.tar.gz: OK
tar xzf apache-tomcat-11.0.26.tar.gz
export CATALINA_HOME="$PWD/apache-tomcat-11.0.26"
export CATALINA_BASE="$PWD/bookshelf-base"
mkdir -p "$CATALINA_BASE"/{conf,logs,temp,webapps,work}
cp "$CATALINA_HOME"/conf/* "$CATALINA_BASE"/conf/
cp target/bookshelf.war "$CATALINA_BASE"/webapps/
"$CATALINA_HOME"/bin/catalina.sh run # Ctrl+C stops it
Source: Apache Tomcat 11: Setup
Then open http://localhost:8080/bookshelf/ for the catalogue, /bookshelf/runtime.xhtml for the implementation report (remove that page
before a real deployment) and /bookshelf/note.xhtml for the validation form. Tomcat unpacks the WAR into webapps/bookshelf and starts
it; stop the server before you replace the files of an exploded deployment.
-
Context root. The WAR file name decides it (
bookshelf.warbecomes/bookshelf). Parallel versions and the root context follow Tomcat’s own naming rules, described in the Context documentation. -
Logs.
logs/catalina.<date>.logandlogs/localhost.<date>.loghold the Weld, Faces and servlet messages used on this page; withcatalina.sh runthey also go to the console. -
Containers. A Tomcat image from a registry works the same way: copy the WAR into the image’s
webapps. The Docker pages of this documentation cover images and the deployment and clustering page covers the Faces side of containers.
Embedded Tomcat
Tomcat can run inside your own main, which is the usual way to run the same WAR from a test or from a Java launcher. The launcher below
starts the exploded WAR that Maven leaves in target/bookshelf.
<?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-launcher</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<tomcat.version>11.0.26</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- the default web.xml that addWebapp installs declares the JSP servlet -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
</project>
package org.example.bookshelf.launcher;
import java.io.File;
import org.apache.catalina.startup.Tomcat;
/** Starts an exploded Bookshelf WAR in embedded Tomcat: EmbeddedBookshelf target/bookshelf 8080 */
public class EmbeddedBookshelf {
public static void main(String[] args) throws Exception {
File webapp = new File(args[0]).getAbsoluteFile();
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(new File("target/tomcat").getAbsolutePath());
tomcat.setPort(Integer.parseInt(args[1]));
tomcat.getConnector();
if (args.length > 2 && args[2].equals("naming")) {
tomcat.enableNaming(); // JNDI is off in embedded Tomcat unless you ask for it
}
tomcat.addWebapp("/bookshelf", webapp.getPath());
tomcat.start();
tomcat.getServer().await();
}
}
Source: Apache Tomcat 11 API: org.apache.catalina.startup.Tomcat and Weld 6.0.4.Final reference: Embedded Tomcat
mvn package # in launcher/, compiles the class
mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt # the Tomcat classpath
java -cp "target/classes:$(cat cp.txt)" org.example.bookshelf.launcher.EmbeddedBookshelf ../bookshelf/target/bookshelf 8080
-
Weld started through its
ServletContainerInitializeras in the standalone server; the programmaticctx.addApplicationListener("org.jboss.weld.environment.servlet.Listener")that the Weld guide shows for a context created withaddContextwas not needed withaddWebapp. If you build the context by hand withaddContext, which skips Tomcat’s web.xml and annotation processing, the guide’s listener registration is required (that variant was not run). -
tomcat-embed-jasperis in the launcher becauseaddWebappinstalls Tomcat’s default web.xml, which declares the JSP servlet. Without it the application still ran but loggedSEVERE: Servlet [jsp] in web application [/bookshelf] threw load() exception. -
JNDI is off in embedded Tomcat. The third argument
namingof the launcher callstomcat.enableNaming(); only then is theMETA-INF/context.xmlbinding applied (The BeanManager Binding in META-INF/context.xml). Without it both implementations usedCDI.current()and the application worked. Only Mojarra was run in the embedded launcher.
Jetty 12
Eclipse Jetty 12 is the other common Servlet container for Faces. The Weld guide says Jetty 12 and newer are supported, that context
activation and injection into servlets, filters and listeners work out of the box, and that a standalone Jetty needs its CDI module;
it also warns that Jetty is not regularly tested with Weld. Jetty 12.1.14 with the ee11 modules (Servlet 6.1) ran the Bookshelf WAR on
both implementations. The base below is the set used; the CDI and WebSocket modules were shown to be required (see the list after the commands),
and ee11-apache-jsp is there because it supplies the Expression Language API and implementation (Jetty has no EL of its own: the diagnostic
showed jakarta.el-api-6.0.1.jar, which that module lists, as the source of jakarta.el.ExpressionFactory).
curl -fLO https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-home/12.1.14/jetty-home-12.1.14.tar.gz
tar xzf jetty-home-12.1.14.tar.gz
export JETTY_HOME="$PWD/jetty-home-12.1.14"
mkdir jetty-base && cd jetty-base
java -jar "$JETTY_HOME"/start.jar --add-modules=http,ee11-deploy,ee11-annotations,ee11-cdi,ee11-apache-jsp,ee11-websocket-jakarta
cp /path/to/bookshelf.war webapps/
java -jar "$JETTY_HOME"/start.jar jetty.http.port=8080
What the runs showed:
-
Weld logged
WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filtersinstead of the Tomcat line, and the validation form, the catalogue and the session counter behaved as on Tomcat. -
The
ee11-cdimodule is required. Without it Weld loggedWELD-ENV-001001: No supported servlet container detected, CDI injection will NOT be available in Servlets, Filters or Listeners, the context failed to start withIllegalStateException: Unable to locate CDIProvider(MyFaces run) and every request answered 503. -
The WebSocket module is required even if you never use
f:websocket. Withoutee11-websocket-jakarta, Weld’s bootstrap failed with aDefinitionExceptioncaused byClassNotFoundException: jakarta.websocket.CloseReason$CloseCode(Mojarra, fromcom.sun.faces.push.WebsocketSessionManager) or by the load error oforg.apache.myfaces.push.cdi.WebsocketSession(MyFaces): both implementations register CDI beans for the push support, and Weld loads them at start-up. On Tomcat the WebSocket API is always present, which is why the Mojarra README’s "optional" holds there. -
The
resource-env-refmust go. The WAR with theBeanManagerreference from The BeanManager Binding in META-INF/context.xml failed withIllegalStateException: Nothing to bind for name BeanManagerand answered 503. Jetty ignoresMETA-INF/context.xml, so the same WAR without theweb.xmlentry runs on both servers. In the Jetty runsjava:comp/env/BeanManagerwas not bound, and both implementations cached theBeanManagerProxy. -
The Jetty log carries many
WARN ... scanned from multiple locationslines about the Servlet API classes that several Jetty JARs contain; they appeared on every start and did not affect the application. -
Embedded Jetty was not run; the Weld guide says it needs no further configuration.
Tomcat Settings That Matter for Faces
The remaining settings are the ones this page ran. The security, configuration, state-saving and exception-handling pages cover the Faces-level equivalents.
Skip TLD Scanning of the Faces JARs
At start-up Tomcat’s TldScanner opens every JAR of the WAR looking for tag library descriptors and logs INFO ... At least one JAR was scanned for
TLDs yet contained no TLDs when some had none. Facelets does not use TLD files, so the scan is wasted work. A JarScanFilter in the same
context.xml skips the JARs; the line disappeared on both implementations and everything else worked. The effect on start-up time was not measured.
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- Binds Weld's BeanManager at java:comp/env/BeanManager -->
<Resource name="BeanManager"
auth="Container"
type="jakarta.enterprise.inject.spi.BeanManager"
factory="org.jboss.weld.resources.ManagerObjectFactory"/>
<!-- Facelets does not use TLD files: do not scan the JARs of the WAR for them -->
<JarScanner>
<JarScanFilter tldSkip="${tomcat.util.scan.StandardJarScanFilter.jarsToSkip},jakarta.faces-*.jar,myfaces-*.jar,weld-servlet-shaded-*.jar,jandex-*.jar,hibernate-validator-*.jar,parsson-*.jar,jakarta.json-api-*.jar,jakarta.validation-api-*.jar,classmate-*.jar,jboss-logging-*.jar"/>
</JarScanner>
</Context>
The pattern ${tomcat.util.scan.StandardJarScanFilter.jarsToSkip} expands to the list in conf/catalina.properties, so Tomcat’s own skip list is
kept and your JARs are appended.
Sessions and Cookies
A Faces application stores the view state in the session unless you use client state, so the session cookie is the credential. These
elements in web.xml keep the identifier out of URLs and script-readable code. In the run, the response carried Set-Cookie: JSESSIONID=...; Path=/bookshelf; Secure; HttpOnly
and the form action was /bookshelf/note.xhtml; without tracking-mode the first response of a client without a cookie carried ;jsessionid=... in the action.
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
Source: Jakarta Servlet 6.1 specification and web-app 6.1 schema
A Secure cookie is only returned over HTTPS, so terminate TLS in front of Tomcat (or in its connector) before you set it, and test the
login and the postbacks over HTTPS. Security covers cookies, CSRF, headers and authentication, and State Saving covers the
client-state key (which on Tomcat is the JNDI entry java:comp/env/faces/ClientSideSecretKey for Mojarra or org.apache.myfaces.SECRET for MyFaces; set
it from the environment, never in a file in the repository).
Production Hardening of Tomcat Itself
Tomcat ships with web applications and settings meant for a development machine. Its security how-to recommends removing what you do not use
and says the following about the defaults: the examples application should always be removed from a security-sensitive installation, the
docs and ROOT applications identify the Tomcat version and are normally removed from a public instance, and the manager and host-manager
applications allow remote deployment and are frequent targets, so leave them out unless you need them and then follow the guide’s section on securing
them. The shutdown port in server.xml should be disabled (port="-1" on the Server element) or protected with a strong password. A CATALINA_BASE
created as above contains none of these applications because you created webapps yourself. The error page that Tomcat prints for an unhandled exception
shows a stack trace; Exception Handling shows error-page mappings and the ErrorReportValve settings.
Troubleshooting
Every message below was produced by the variant named in the second column; the fixes were tried.
| Symptom | Cause | Fix |
|---|---|---|
The context fails to start with |
Mojarra 4.1 without a CDI container, or with Weld 5 (CDI 4.0) |
Add |
The context fails to start with |
MyFaces without a CDI container |
Add |
|
No |
Add the file (beans.xml and Bean Discovery). |
HTTP 500 with |
Bean not discovered: no |
Add |
Constraints on Faces input are ignored without any message |
No Bean Validation provider in the WAR |
Add |
|
|
Publish the CDI-aware factory from a listener (Bean Validation page). |
|
No JSON-P implementation |
Add |
|
Weld 7 (CDI 5) with Faces 4.1 |
Use Weld 6.0.x. |
Jetty: 503, |
The |
|
Jetty: Weld |
The WebSocket module is not enabled |
|
Jetty: 503, |
|
Remove it from the WAR for Jetty. |
Tomcat: |
|
Put the |
Embedded: |
The JSP servlet of the default web.xml has no Jasper |
Add |
MyFaces on undeploy: |
MyFaces destroys itself after Weld has shut down |
Expected with 4.1.4 and Weld 6.0.4; no fix tried (The BeanManager Binding in META-INF/context.xml). |
|
Information only |
Optional: |
Mojarra vs MyFaces on Tomcat
-
Start-up without a
beans.xml. Mojarra starts and fails at the first expression; MyFaces logs two start-up messages and fails the same way (beans.xml and Bean Discovery). -
BeanManager lookup. Mojarra looks it up on demand and caches it under
com.sun.faces.cdi.BeanManager; MyFaces looks it up once at start-up and stores it underoam.cdi.BEAN_MANAGER_INSTANCE; both try JNDI beforeCDI.current()(The BeanManager Binding in META-INF/context.xml). -
Shutdown. MyFaces 4.1.4 logs
SEVEREentries while destroying itself on Tomcat with Weld 6.0.4; Mojarra does not. -
Weld versions. Mojarra 4.1 needs Weld 6 (it fails with Weld 5); MyFaces 4.1.4 ran with Weld 5.1.7 and 6.0.4 and failed with Weld 7.0.0.
-
JSON Processing. Mojarra needs a JSON-P implementation to push; MyFaces did not in this run. The wire formats differ.
-
Messages.
h:messagesshowed the bare summary on Mojarra andclientId: summaryon MyFaces for the same validation failure. -
Jetty. Both need the CDI and WebSocket modules and both fail with the
resource-env-ref. -
Weld servlet attribute. Mojarra falls back to the attribute that Weld Servlet sets on the servlet context (
org.jboss.weld.environment.servlet.jakarta.enterprise.inject.spi.BeanManager); MyFaces reads the attribute namedjakarta.enterprise.inject.spi.BeanManager, which wasnullhere, and thenCDI.current().
What Changed
-
JSF 1.x and 2.0 to 2.2 on Tomcat. The API and implementation JARs, the JSTL JARs and a
web.xmlwith the Faces servlet; managed beans came fromfaces-config.xml, so no CDI container was needed. -
Faces 2.3 (Java EE 8). Faces became a CDI citizen:
@Injecton Faces artefacts, CDI evaluation of the implicit objects,f:websocketwith@Push,@FacesConfig, and the deprecation of the managed-bean facility (see the versions page). A Servlet container had to bring a CDI container to use them. -
Faces 4.0 / Jakarta EE 10 (Tomcat 10.1, Weld 5, Mojarra 4.0, MyFaces 4.0). The
jakarta.*namespace; JSP support and managed beans removed, so a CDI container is mandatory on Tomcat. Mojarra 4.0.26 ran with Weld 5.1.7 and 6.0.4 on Tomcat 11.0.26 here; Tomcat 10.1 itself was not run. -
Faces 4.1 / Jakarta EE 11 (Tomcat 11, Weld 6, Mojarra 4.1, MyFaces 4.1). Java 17 minimum; Mojarra 4.1 needs the CDI 4.1 API (
ELAwareBeanManager), so Weld 6 and not Weld 5. Weld 6.0.4 is the pairing used on this page. -
Faces 5.0 / Jakarta EE 12 (upcoming). Only milestones exist (
jakarta.faces-api5.0.0-M9); Weld 7 is the CDI 5 line. Nothing here describes 5.0 behaviour, and Weld 7 failed with MyFaces 4.1.4. -
Jetty. The module names carry the Jakarta EE level (
ee10-cdiin the Weld guide,ee11-cdiin Jetty 12.1.14).
Where the Books Are Outdated
The books of this section’s bibliography that deal with a servlet container were written for Tomcat 5 and 6, JSF 1.x and 2.0 and a
javax.* world. I checked what they say about it in the copies the section was written from; in prose and without reproducing their
listings:
-
The JAR lists. Mann’s JavaServer Faces in Action (2004) develops its examples on Tomcat 5.0 and has you copy the JSF API and implementation JARs,
jstl.jarandstandard.jarintoWEB-INF/lib. Dudney et al.'s Mastering JavaServer Faces (2004) lists the API and reference-implementation JARs, the JSTL JARs and four Apache Commons JARs as required resources, and suggests Tomcat’s shared library directory for the API JAR. Burns and Schalk’s JavaServer Faces 2.0: The Complete Reference (2010) asks for a Servlet 2.5 container and names Tomcat 5.x, and uses thejsf-apiandjsf-implMaven artifacts. Today the dependency set is thepom.xmlabove: one or two Faces JARs, a Weld JAR, optional add-ons; Facelets needs no JSTL JARs; the packages arejakarta.faces; and the Servlet API and EL come from Tomcat. -
No CDI container. None of these books has a CDI step, because the managed-bean facility of
faces-config.xmldid the work. Faces 4.x has no such facility, so a Servlet container without Weld cannot start Mojarra 4.1 or MyFaces 4.1 (Which Combinations Work). -
Container versions. Leonard’s JSF 2.0 Cookbook (2010) gives separate setup notes for older Servlet containers (Tomcat 5, Jetty 5) and for Servlet containers with JSP 2.1 (Tomcat 6, Jetty 6). Those releases and the
javax.*APIs they carry cannot run thejakarta.*Faces releases of this page, which need Tomcat 10.1 or later for Faces 4.0 and Tomcat 11 for the versions used here. -
JSP views. The 2004 books use JSP as the view technology on Tomcat. Facelets (XHTML) replaced it, and JSP support was removed in Faces 4.0.
-
Deployment by hand. The books describe copying files into the container and, in one case, containers' shared class directories; the build is Maven, the WAR is self-contained, and the Tomcat default web applications should be removed (Tomcat Settings That Matter for Faces).
Pitfalls
-
Shipping Servlet, EL or CDI API JARs in the WAR next to Tomcat’s own. Servlet, JSP, EL and WebSocket classes always come from Tomcat; a CDI API JAR in
WEB-INF/libconflicts with the one insideweld-servlet-shaded. Useprovidedscope for the compile-time API JAR. -
Forgetting
WEB-INF/beans.xml. Nothing is discovered (beans.xml and Bean Discovery). -
A scope-less
@Namedclass. With the defaultannotatedmode it is not a bean; EL silently sees nothing. -
Choosing
alldiscovery to avoid the previous mistake. Every class becomes a bean and class-loading problems show up as Weld warnings. -
Assuming Bean Validation is there. On Tomcat the constraints do nothing until a provider is in the WAR.
-
Putting the Tomcat-only
resource-env-refin a WAR that also goes to Jetty. The Jetty deployment fails. -
Treating the MyFaces undeploy errors as application bugs. They come from the shutdown order.
-
Weld 7 or Weld 5 because "newer is better". Match Weld to the Faces release: Weld 6 for Faces 4.1.
-
Leaving the diagnostic servlet and
runtime.xhtmldeployed. They print internal class names; delete them or put a security constraint on them. -
A
Securesession cookie over plain HTTP. The browser does not send it back, so login loops appear to be an application bug. -
EJB,
@PersistenceContextand@Transactionalon Weld Servlet. Not supported; use an application server or wire the transaction manager yourself. -
Running Tomcat as root, with the shutdown port open, or with the default web applications. See the Tomcat security how-to.
Related Pages
The Jakarta EE Servers page is the alternative with a bundled implementation. The Spring Boot with JoinFaces page and the
Quarkus page cover the runtimes without a web.xml, the Persistence and Services page covers data access without a server-provided transaction manager, the
Testing page covers running these WARs from Arquillian, Selenium and Playwright, and the Deployment and Clustering page covers packaging, containers and session
replication for the Tomcat setup built here. See also
the Maven WAR setup,
beans.xml in the project setup page,
bean discovery,
the ValidatorFactory listener,
enabling f:websocket,
project stages,
cookies and sessions,
client-state keys,
error pages,
the version matrix and
the first application on Tomcat,
Migrating to Jakarta Faces (moving an older Tomcat application) and
the runtime rules of the best-practices checklist.