Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 23 – Jakarta Faces (JSF): component-based web UIs

Jakarta Faces 4.1: a first Facelets app

Build a first Jakarta Faces 4.1 app on Tomcat 11: Mojarra and Weld in the WAR, a Facelets form bound to a CDI bean, and what changes for JSF 2 code.

  • Intermediate
  • 22 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 22 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain what Faces adds over servlets and JSP, from components to the view state
  • Run Mojarra 4.1 with Weld on Tomcat 11, or choose a server that ships Faces
  • Write a Facelets page backed by a CDI @Named bean, and validate what it receives
  • Recognise JSF 2 code that no longer works on Faces 4.1, and replace it

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain what Faces adds over servlets and JSP, from components to the view state
  • Run Mojarra 4.1 with Weld on Tomcat 11, or choose a server that ships Faces
  • Write a Facelets page backed by a CDI @Named bean, and validate what it receives
  • Recognise JSF 2 code that no longer works on Faces 4.1, and replace it

Before you start

On this page

Jakarta Faces, called JavaServer Faces (JSF) for most of its history and renamed in version 4.0, is the Jakarta EE framework for web pages built from server-side components. A page is a Facelets file: XHTML whose tags, such as h:form, h:inputText and h:commandButton, stand for components. When a request arrives, Faces builds a tree of those components, or restores the tree it saved for the page, lets each component read its part of the posted form, converts and validates the values, stores them in a Java object and calls the method of the button that was pressed. Then the components write the HTML. Expressions such as #{visitor.username} connect them to the properties and methods of beans that CDI creates. Faces 4.1 is the version in Jakarta EE 11.

Tomcat 11 implements Servlet 6.1, Pages 4.0, Expression Language 6.0, WebSocket 2.2 and Authentication 3.1, but neither Faces nor CDI, so a Faces application for Tomcat carries both in its WAR: Mojarra 4.1, the Eclipse Foundation’s implementation of Faces, and Weld 6.0 for CDI. This lesson builds such an application, runs it on Tomcat 11.0.26 with Eclipse Temurin 25.0.4.1 and records every request, including the two failures that stop most first attempts.

What Faces does that servlets and JSP leave to you

A servlet receives a form as strings. It reads each parameter, converts it, checks it, collects a message for every field that was wrong, and writes the page again with the user’s input still in it. Faces moves each of those jobs into the components of the page and runs them in a fixed order, the request life cycle, which the next lesson follows phase by phase.

The job Servlets and JSP Faces
Read a posted field request.getParameter("username") in your servlet The h:inputText component reads its own field
Turn text into a number or a date Your code, with its own error handling A converter attached to the component
Check the value Your code required, validator tags or Bean Validation
Report what was wrong Your code hands messages to the page Faces queues a message per field, and h:messages shows them
Show the input again after an error Your code copies it back into every field Each component keeps the value it was sent
Remember the form between requests Hidden fields or the session, by hand The view state, saved by Faces
Run the code of the button that was pressed Your servlet works out which button it was The method bound to the button’s action

The price is memory and a framework to learn. The server keeps a saved view for every page a user has open, and a request passes through more machinery than a servlet’s doPost. In return, form-heavy applications, such as administration screens and internal tools, stop repeating the same parsing and checking code on every page.

Where Faces runs

Mojarra’s README describes two kinds of server. A full Jakarta EE server, such as WildFly, Payara, GlassFish or Liberty, ships Faces and CDI, so the WAR needs neither; GlassFish 8, the Eclipse Foundation’s server for Jakarta EE 11, needs JDK 21 or later. A servlet container such as Tomcat or Jetty ships neither, so the WAR brings Faces 4.1 and a CDI 4.1 implementation itself.

Two ways to run a Faces 4.1 application
Criterion Tomcat 11 with Faces in the WARA Jakarta EE 11 server
Faces and CDI come from WEB-INF/lib: Mojarra 4.1.18 and Weld 6.0.4The server, for example GlassFish 8
Who picks their versions You, for each applicationThe server release
Persistence, transactions, Enterprise Beans Added one by one; Weld on Tomcat supports no session beansIncluded
When to choose Your team already runs Tomcat, or the application needs little more than FacesThe application uses several Jakarta EE APIs

Faces 4.1 has two implementations: Mojarra, published as org.glassfish:jakarta.faces (the recordings use 4.1.18), and Apache MyFaces Core, whose 4.1 line is at 4.1.4. This module uses Mojarra. Keep Weld on the 6.0 line, as Mojarra’s README asks: Weld 7.0 implements CDI 5.0, the version for Jakarta EE 12, while Faces 4.1 is specified against CDI 4.1 and Faces 5.0 is still under development.

The project: a POM, a bean and a page

The welcome application has five source files:

The welcome application

XML (pom.xml) · welcome/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- The welcome application: one Facelets page and one CDI bean, packaged as a WAR for Tomcat 11. -->
<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>com.example</groupId>
  <artifactId>welcome</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>

  <properties>
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- Mojarra, Eclipse's implementation of Faces 4.1: the API and the implementation in one JAR. Tomcat has no
         Faces of its own, so the JAR goes into WEB-INF/lib. -->
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>jakarta.faces</artifactId>
      <version>4.1.18</version>
    </dependency>
    <!-- Weld, the CDI container that creates the page's beans. Faces 4.1 needs CDI 4.1, which Weld 6.0 implements;
         the shaded JAR bundles the CDI API with Weld itself. -->
    <dependency>
      <groupId>org.jboss.weld.servlet</groupId>
      <artifactId>weld-servlet-shaded</artifactId>
      <version>6.0.4.Final</version>
    </dependency>
  </dependencies>

  <build>
    <!-- The WAR's name, and so the application's context path: /welcome. -->
    <finalName>welcome</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.5.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.5.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.16.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.6.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.5.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Java · welcome/src/main/java/com/example/welcome/Visitor.java

package com.example.welcome;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;

/**
 * The backing bean of welcome.xhtml. @Named gives it the name the page's expressions use, "visitor" (the class name
 * with a lower-case first letter); @RequestScoped makes CDI create a new instance for every request.
 */
@Named
@RequestScoped
public class Visitor {

    private String username;
    private String greeting;

    // #{visitor.username} reads the property through the getter when the page is rendered, and a posted form writes
    // it through the setter.
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getGreeting() {
        return greeting;
    }

    // The action of the Enter button. Faces calls it only after the username has been validated and stored.
    public void greet() {
        greeting = "Welcome " + username;
    }
}

XHTML · welcome/src/main/webapp/welcome.xhtml

<!DOCTYPE html>
<!-- A Facelets page. Faces reads it as XML, so every tag is closed, and the prefix h stands for the namespace of the
     standard HTML components, jakarta.faces.html. -->
<html lang="en" xmlns:h="jakarta.faces.html">
<h:head>
  <title>Welcome</title>
</h:head>
<h:body>
  <h1>Welcome</h1>
  <h:form id="login">
    <h:outputLabel for="username" value="Username"/>
    <!-- value binds the field to the bean's property; label names the field in messages. -->
    <h:inputText id="username" label="Username" value="#{visitor.username}" required="true"/>
    <h:commandButton id="enter" value="Enter" action="#{visitor.greet}"/>
  </h:form>
  <!-- One message for each value of the last post that failed conversion or validation. -->
  <h:messages id="messages"/>
  <!-- An expression in the text is written out like h:outputText: any HTML in the value is escaped. -->
  <p id="greeting">#{visitor.greeting}</p>
</h:body>
</html>

XML (web.xml) · welcome/src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Maps the FacesServlet to *.xhtml only. Without a mapping of its own, Faces maps itself to *.xhtml, *.jsf, *.faces
     and /faces/*, and every page answers at four addresses. -->
<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">
  <!-- Facelets sends the XML comments of a page to the browser unless this parameter is true. -->
  <context-param>
    <param-name>jakarta.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param>
  <servlet>
    <servlet-name>facesServlet</servlet-name>
    <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>facesServlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
  <!-- /welcome/ shows welcome.xhtml. -->
  <welcome-file-list>
    <welcome-file>welcome.xhtml</welcome-file>
  </welcome-file-list>
</web-app>

XML (beans.xml) · welcome/src/main/webapp/WEB-INF/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- beans.xml makes WEB-INF/classes a bean archive: Weld on Tomcat looks for beans only in archives that have one.
     With bean-discovery-mode="annotated", the default since CDI 4.0, it finds the classes that carry a scope
     annotation such as @RequestScoped. -->
<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">
</beans>
  • pom.xml packages a WAR and declares two dependencies with the default scope, so Maven copies both JARs into WEB-INF/lib: org.glassfish:jakarta.faces, which holds the Faces API and Mojarra in one file, and weld-servlet-shaded, which bundles Weld with the CDI API. No servlet dependency is needed, because the Java code uses only CDI annotations.
  • Visitor.java is the backing bean. @Named gives it the name the page uses, visitor: the class name with a lower-case first letter. @RequestScoped makes CDI create a new instance for every request. The getter and setter make username a property that an expression can read and write, and greet() is the button’s action. It returns nothing, so Faces finds no navigation case and shows the same page again.
  • welcome.xhtml declares the prefix h for the namespace jakarta.faces.html. h:head and h:body give Faces a place for the scripts and style sheets its components may need. Inside h:form each id is combined with the form’s, so the field reaches the HTML as login:username. required="true" rejects an empty field, and label names the field in its messages.
  • web.xml maps the FacesServlet to *.xhtml, turns on jakarta.faces.FACELETS_SKIP_COMMENTS, without which Facelets sends the XML comments of a page to the browser, and makes /welcome/ show the page.
  • beans.xml makes the application’s classes a bean archive. In annotated mode, the default since CDI 4.0, Weld turns the classes that carry a scope annotation such as @RequestScoped into beans.

Building and starting it on Tomcat

The session builds the WAR, lists it, deploys it to a new Tomcat base that listens on 127.0.0.1, and asks for the application’s address:

mvn package, the WAR, the start-up log and the first page Python · build_session.py
from session import run

# Builds the welcome WAR and lists what it carries, deploys it to a new Tomcat base on 127.0.0.1:28640, shows the log
# lines in which Weld and Mojarra start, asks for the application's address, then for the page at the address the
# FacesServlet is mapped to and at the three others Faces would map itself to.
run(
    """
cd welcome
mvn -q package
jar tf target/welcome.war
cd ..
sh new-base.sh base 28640
cp welcome/target/welcome.war base/webapps/
$CATALINA_HOME/bin/startup.sh
grep -o -E "WELD-000900.*|WELD-ENV-001100.*|Initializing Mojarra.*|Deployment of .*" base/logs/catalina.out
curl -s http://127.0.0.1:28640/welcome/
for page in welcome.xhtml welcome.jsf welcome.faces faces/welcome.xhtml; do curl -s -o /dev/null -w "%{http_code} /welcome/$page\\n" http://127.0.0.1:28640/welcome/$page; done
$CATALINA_HOME/bin/shutdown.sh
""",
    port=28640,
)

Output

# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0 · Apache Tomcat 11.0.26
$ cd welcome
$ mvn -q package
$ jar tf target/welcome.war
META-INF/
META-INF/MANIFEST.MF
WEB-INF/
WEB-INF/classes/
WEB-INF/classes/com/
WEB-INF/classes/com/example/
WEB-INF/classes/com/example/welcome/
WEB-INF/lib/
WEB-INF/beans.xml
WEB-INF/classes/com/example/welcome/Visitor.class
WEB-INF/lib/jakarta.annotation-api-3.0.0.jar
WEB-INF/lib/jakarta.faces-4.1.18.jar
WEB-INF/lib/weld-servlet-shaded-6.0.4.Final.jar
WEB-INF/web.xml
welcome.xhtml
META-INF/maven/com.example/welcome/pom.xml
META-INF/maven/com.example/welcome/pom.properties
$ cd ..
$ sh new-base.sh base 28640
$ cp welcome/target/welcome.war base/webapps/
$ $CATALINA_HOME/bin/startup.sh
$ grep -o -E "WELD-000900.*|WELD-ENV-001100.*|Initializing Mojarra.*|Deployment of .*" base/logs/catalina.out
WELD-000900: 6.0.4 (Final)
WELD-ENV-001100: Tomcat 7+ detected, CDI injection will be available in Servlets, Filters and Listeners.
Initializing Mojarra 4.1.18 for context '/welcome'
Deployment of web application archive [base/webapps/welcome.war] has finished in […] ms
$ curl -s http://127.0.0.1:28640/welcome/
<!DOCTYPE html>
<html lang="en"><head>
  <title>Welcome</title></head><body>
  <h1>Welcome</h1>
<form id="login" name="login" method="post" action="/welcome/welcome.xhtml;jsessionid=<id>" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="login" value="login" />
<label for="login:username">Username</label><input id="login:username" type="text" name="login:username" value="" /><input id="login:enter" type="submit" name="login:enter" value="Enter" /><input type="hidden" name="jakarta.faces.ViewState" id="j_id1:jakarta.faces.ViewState:0" value="…" autocomplete="one-time-code" />
</form><div id="messages"></div>
  
  <p id="greeting"></p></body>
</html>
$ for page in welcome.xhtml welcome.jsf welcome.faces faces/welcome.xhtml; do curl -s -o /dev/null -w "%{http_code} /welcome/$page\n" http://127.0.0.1:28640/welcome/$page; done
200 /welcome/welcome.xhtml
404 /welcome/welcome.jsf
404 /welcome/welcome.faces
404 /welcome/faces/welcome.xhtml
$ $CATALINA_HOME/bin/shutdown.sh

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_session.py

  • WEB-INF/lib holds three JARs: Mojarra 4.1.18, Weld 6.0.4 and jakarta.annotation-api, which both of them declare as a dependency.
  • The log shows Weld 6.0.4 starting and recognising Tomcat, and Mojarra 4.1.18 initialising the context /welcome.
  • The page arrived as plain HTML. The h: tags became form, label and input elements, h:messages an empty div with its id, and the form gained two hidden fields. The form’s action ends in ;jsessionid=<id> because this first response also started an HTTP session, and Tomcat could not yet know whether the browser keeps cookies.
  • Only welcome.xhtml answered. The mapping in web.xml replaced the four that Faces registers for an application without one, *.xhtml, *.jsf, *.faces and /faces/*, so every page has one address.
A request for welcome.xhtml reaches the FacesServlet, which builds a component tree bound to the Visitor bean and renders the HTML.Request from the browserGET or POST /welcome/welcome.xhtmlTomcat 11: /welcomeResponse to the browser:the HTML, with a hiddenjakarta.faces.ViewState fieldFacesServletmapped to *.xhtmlComponent treeVisitor bean@Named @RequestScopedcreated by WeldHTTP sessionkeeps the saved viewbetween requestsUIViewRootHtmlForm loginHtmlMessages messagesHtmlInputText usernameHtmlCommandButton enterbuilds or restores#{visitor.username}#{visitor.greet}rendered

One request to the welcome application, from the browser to the bean and back

Text description of the diagram

The diagram is one column of boxes, read from top to bottom.

  1. At the top, a request from the browser: a GET or a POST for /welcome/welcome.xhtml. An arrow leads into the box for Tomcat 11 and the application /welcome.
  2. Inside Tomcat, the request reaches the FacesServlet, which the application maps to *.xhtml. An arrow labelled "builds or restores" leads to the component tree: the FacesServlet builds the tree on the first request and restores the saved tree when the form is posted.
  3. The component tree is drawn as nested boxes. Its root is a UIViewRoot. Inside it are an HtmlForm with the id login, which itself holds an HtmlInputText with the id username and an HtmlCommandButton with the id enter, and, outside the form, an HtmlMessages component with the id messages.
  4. An arrow from the tree to the Visitor bean is labelled with the page's two expressions, #{visitor.username} and #{visitor.greet}. The bean is marked @Named and @RequestScoped, and Weld creates it.
  5. Below the bean is the HTTP session, which keeps the saved view between requests.
  6. An arrow labelled "rendered" leaves Tomcat for the response at the bottom: the HTML of the page, with a hidden jakarta.faces.ViewState field that the next post sends back.

What the browser sends back

curl can play the browser: it keeps the session cookie, and the hidden fields of the form go back with each post.

A GET, then two posts with curl Python · post_session.py
from session import run

# Plays the browser with curl: loads the page, keeps the session cookie and the form's hidden fields, then posts the
# form back twice, once with a username and once with an empty field, and shows the two parts of each response that
# change: the messages and the greeting.
run(
    """
cd welcome
mvn -q package
cd ..
sh new-base.sh base 28640
cp welcome/target/welcome.war base/webapps/
$CATALINA_HOME/bin/startup.sh
curl -s -c cookies.txt -o page.html http://127.0.0.1:28640/welcome/welcome.xhtml
grep -o '<input type="hidden"[^>]*>' page.html
sed -n 's/.*name="jakarta.faces.ViewState"[^>]*value="\\([^"]*\\)".*/\\1/p' page.html > state.txt
curl -s -b cookies.txt -d login=login -d login:username=asha_p -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" http://127.0.0.1:28640/welcome/welcome.xhtml | grep -o -E '<(div|ul) id="messages">.*</(div|ul)>|<p id="greeting">.*</p>'
curl -s -b cookies.txt -d login=login -d login:username= -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" http://127.0.0.1:28640/welcome/welcome.xhtml | grep -o -E '<(div|ul) id="messages">.*</(div|ul)>|<p id="greeting">.*</p>'
$CATALINA_HOME/bin/shutdown.sh
""",
    port=28640,
)

Output

# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0 · Apache Tomcat 11.0.26
$ cd welcome
$ mvn -q package
$ cd ..
$ sh new-base.sh base 28640
$ cp welcome/target/welcome.war base/webapps/
$ $CATALINA_HOME/bin/startup.sh
$ curl -s -c cookies.txt -o page.html http://127.0.0.1:28640/welcome/welcome.xhtml
$ grep -o '<input type="hidden"[^>]*>' page.html
<input type="hidden" name="login" value="login" />
<input type="hidden" name="jakarta.faces.ViewState" id="j_id1:jakarta.faces.ViewState:0" value="…" autocomplete="one-time-code" />
$ sed -n 's/.*name="jakarta.faces.ViewState"[^>]*value="\([^"]*\)".*/\1/p' page.html > state.txt
$ curl -s -b cookies.txt -d login=login -d login:username=asha_p -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" http://127.0.0.1:28640/welcome/welcome.xhtml | grep -o -E '<(div|ul) id="messages">.*</(div|ul)>|<p id="greeting">.*</p>'
<div id="messages"></div>
<p id="greeting">Welcome asha_p</p>
$ curl -s -b cookies.txt -d login=login -d login:username= -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" http://127.0.0.1:28640/welcome/welcome.xhtml | grep -o -E '<(div|ul) id="messages">.*</(div|ul)>|<p id="greeting">.*</p>'
<ul id="messages"><li>	Username: Validation Error: Value is required. </li></ul>
<p id="greeting"></p>
$ $CATALINA_HOME/bin/shutdown.sh

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 post_session.py

  • login=login tells Faces which form of the page was posted.
  • jakarta.faces.ViewState names the saved view. By default Faces saves views on the server, in the HTTP session, and the field only identifies the saved copy. With the context parameter jakarta.faces.STATE_SAVING_METHOD set to client, the field carries the saved view itself.
  • login:enter=Enter is the button that was pressed, so Faces knows which action to run.

The first post stored asha_p in the bean and ran greet(): the page came back with “Welcome asha_p” and an empty div for messages. The second post sent an empty field, and the page came back with “Username: Validation Error: Value is required.” and an empty greeting. Validation failed, so Faces neither stored the value nor called the action, and rendered the page with the message instead.

cURL to Code Converter Paste one of the posts above to see the same request written with Java's HttpClient, which is how the exercise's checks send it.

When the page loads but the first post fails

The same application, with its beans.xml removed:

The welcome application without beans.xml Python · nobeans_session.py
from session import run

# The welcome application without its beans.xml: built, deployed and used as in the other sessions. The page still
# loads; the first post fails.
run(
    """
cd welcome
rm src/main/webapp/WEB-INF/beans.xml
mvn -q package
cd ..
sh new-base.sh base 28640
cp welcome/target/welcome.war base/webapps/
$CATALINA_HOME/bin/startup.sh
grep -o "WELD-ENV-000028.*" base/logs/catalina.out
curl -s -c cookies.txt -o page.html -w "%{http_code}\\n" http://127.0.0.1:28640/welcome/welcome.xhtml
sed -n 's/.*name="jakarta.faces.ViewState"[^>]*value="\\([^"]*\\)".*/\\1/p' page.html > state.txt
curl -s -b cookies.txt -d login=login -d login:username=asha_p -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" -o error.html -w "%{http_code}\\n" http://127.0.0.1:28640/welcome/welcome.xhtml
grep -h -o "threw exception .*" base/logs/localhost.*.log
$CATALINA_HOME/bin/shutdown.sh
""",
    port=28640,
)

Output

# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0 · Apache Tomcat 11.0.26
$ cd welcome
$ rm src/main/webapp/WEB-INF/beans.xml
$ mvn -q package
$ cd ..
$ sh new-base.sh base 28640
$ cp welcome/target/welcome.war base/webapps/
$ $CATALINA_HOME/bin/startup.sh
$ grep -o "WELD-ENV-000028.*" base/logs/catalina.out
WELD-ENV-000028: Weld initialization skipped - no bean archive found
$ curl -s -c cookies.txt -o page.html -w "%{http_code}\n" http://127.0.0.1:28640/welcome/welcome.xhtml
200
$ sed -n 's/.*name="jakarta.faces.ViewState"[^>]*value="\([^"]*\)".*/\1/p' page.html > state.txt
$ curl -s -b cookies.txt -d login=login -d login:username=asha_p -d login:enter=Enter --data-urlencode "jakarta.faces.ViewState=$(cat state.txt)" -o error.html -w "%{http_code}\n" http://127.0.0.1:28640/welcome/welcome.xhtml
500
$ grep -h -o "threw exception .*" base/logs/localhost.*.log
threw exception [/welcome.xhtml @13,94 value="#{visitor.username}": Target Unreachable, identifier [visitor] resolved to null] with root cause
$ $CATALINA_HOME/bin/shutdown.sh

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nobeans_session.py

Weld on Tomcat looks for beans only in archives that have a beans.xml, so it skipped this one (WELD-ENV-000028), and no bean named visitor existed. The GET still worked, because an expression whose first name resolves to nothing evaluates to null, which renders as an empty field. The post has to store the value somewhere, so it failed with HTTP 500, and Tomcat’s localhost log named the page, the line and column of the tag, and the expression. Target Unreachable, identifier [visitor] resolved to null therefore means that no bean has that name. Check three things: beans.xml, a scope annotation on the class, since @Named alone does not make a bean in annotated mode, and the spelling of the name in the page.

Code from JSF 2 books

Many books and course notes still teach JSF 2, which wrote the same bean and page like this:

The welcome bean and page as JSF 2 code

Java · jsf2/Visitor.java

package com.example.welcome;

// The bean as JSF 2 books write it: a JSF managed bean, with JSF's own annotations from javax.faces.bean.
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class Visitor {

    private String username;
    private String greeting;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getGreeting() {
        return greeting;
    }

    public void greet() {
        greeting = "Welcome " + username;
    }
}

XHTML · jsf2/welcome.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- The page as JSF 2.2 books write it: the XHTML 1.0 doctype and the xmlns.jcp.org namespaces. -->
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
  <title>Welcome</title>
</h:head>
<h:body>
  <h:form id="login">
    <h:outputLabel for="username" value="Username"/>
    <h:inputText id="username" label="Username" value="#{visitor.username}" required="true"/>
    <h:commandButton id="enter" value="Enter" action="#{visitor.greet}"/>
  </h:form>
  <h:messages id="messages"/>
  <p id="greeting">#{visitor.greeting}</p>
</h:body>
</html>

The session compiles that bean, and the same bean with javax renamed to jakarta, against the Mojarra JAR of the welcome WAR. Then it copies the JSF 2.2 page into the running application and asks for it:

JSF 2 code against Faces 4.1 Python · jsf2_session.py
from session import run

# Code from the JSF 2 era against Faces 4.1: the textbook bean and the same bean with javax renamed to jakarta, each
# compiled with javac against the Mojarra 4.1.18 JAR that the welcome WAR carries; then the JSF 2.2 page, copied into
# the running welcome application next to its own page and requested.
run(
    """
cd welcome
mvn -q package
cd ..
javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/Visitor.java
javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/renamed/Visitor.java
sh new-base.sh base 28640
cp welcome/target/welcome.war base/webapps/
$CATALINA_HOME/bin/startup.sh
cp jsf2/welcome.xhtml base/webapps/welcome/jsf2.xhtml
curl -s http://127.0.0.1:28640/welcome/jsf2.xhtml | grep -o -E '<html[^>]*>|<input id="login:username"[^>]*>'
$CATALINA_HOME/bin/shutdown.sh
""",
    fails=(
        "javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/Visitor.java",
        "javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/renamed/Visitor.java",
    ),
    port=28640,
)

Output

# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0 · Apache Tomcat 11.0.26
$ cd welcome
$ mvn -q package
$ cd ..
$ javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/Visitor.java
jsf2/Visitor.java:4: error: package javax.faces.bean does not exist
import javax.faces.bean.ManagedBean;
                       ^
jsf2/Visitor.java:5: error: package javax.faces.bean does not exist
import javax.faces.bean.RequestScoped;
                       ^
jsf2/Visitor.java:7: error: cannot find symbol
@ManagedBean
 ^
  symbol: class ManagedBean
jsf2/Visitor.java:8: error: cannot find symbol
@RequestScoped
 ^
  symbol: class RequestScoped
4 errors
[exit status 1]
$ javac -d classes -cp welcome/target/welcome/WEB-INF/lib/jakarta.faces-4.1.18.jar jsf2/renamed/Visitor.java
jsf2/renamed/Visitor.java:4: error: package jakarta.faces.bean does not exist
import jakarta.faces.bean.ManagedBean;
                         ^
jsf2/renamed/Visitor.java:5: error: package jakarta.faces.bean does not exist
import jakarta.faces.bean.RequestScoped;
                         ^
jsf2/renamed/Visitor.java:7: error: cannot find symbol
@ManagedBean
 ^
  symbol: class ManagedBean
jsf2/renamed/Visitor.java:8: error: cannot find symbol
@RequestScoped
 ^
  symbol: class RequestScoped
4 errors
[exit status 1]
$ sh new-base.sh base 28640
$ cp welcome/target/welcome.war base/webapps/
$ $CATALINA_HOME/bin/startup.sh
$ cp jsf2/welcome.xhtml base/webapps/welcome/jsf2.xhtml
$ curl -s http://127.0.0.1:28640/welcome/jsf2.xhtml | grep -o -E '<html[^>]*>|<input id="login:username"[^>]*>'
<html xmlns="http://www.w3.org/1999/xhtml">
<input id="login:username" type="text" name="login:username" value="" />
$ $CATALINA_HOME/bin/shutdown.sh

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 jsf2_session.py

Two separate changes broke the bean. Faces 3.0 renamed every package from javax.faces to jakarta.faces, so the first compile found no javax.faces.bean. Faces 4.0 then removed JSF’s own managed beans, @ManagedBean and its scope annotations, so the renamed imports fail too. Beans are CDI beans now:

JSF 2 Faces 4.1
javax.faces.bean.ManagedBean jakarta.inject.Named
javax.faces.bean.RequestScoped jakarta.enterprise.context.RequestScoped
javax.faces.bean.SessionScoped jakarta.enterprise.context.SessionScoped
javax.faces.bean.ViewScoped jakarta.faces.view.ViewScoped
javax.faces.bean.ManagedProperty jakarta.inject.Inject for another bean; jakarta.faces.annotation.ManagedProperty with @Inject for the value of an expression
http://xmlns.jcp.org/jsf/html jakarta.faces.html
http://xmlns.jcp.org/jsf/core jakarta.faces.core
http://xmlns.jcp.org/jsf/facelets jakarta.faces.facelets
JSP pages as views Facelets pages only

The page is a different story. Faces 4.0 replaced the http://xmlns.jcp.org/jsf/ namespaces with jakarta.faces names, and the specification lists the rename among the changes that break compatibility. Yet Mojarra 4.1.18 still rendered the JSF 2.2 page, and its h:inputText became an input element. A page that works only because one implementation is lenient can stop working with the next release or with MyFaces, so replace the namespaces whenever you touch a page.

GTU 3160707, Advance Java Programming

Gujarat Technological University’s syllabus for this subject teaches “Java Server Faces 2.0” in unit 5, and its practical 6 asks for a page that takes a username and, if it is valid, prints “Welcome Username”: the exercise below. Because the syllabus names JSF 2.0, the books it lists use @ManagedBean and the old namespaces; write the same programs with the Faces 4.1 code of this lesson. The table maps the unit to the lessons of this module. MySmartCoPilot has no connection with GTU; for the exact wording of each topic, read the syllabus itself.

Unit 5 topic Lesson of this module
Introduction to JSF, JSF Expression Language, JSF Standard Component This lesson
JSF request processing life cycle The Faces life cycle, phase by phase
JSF Convertor Tag, JSF Validation Tag, JSF Event Handling Faces converters, validators and events
JSF Facelets Tag, Database Access, JSF Libraries: PrimeFaces Facelets templates, data tables and PrimeFaces

Your turn: accept only valid usernames

Make the page accept usernames of 3 to 20 letters, digits or underscores, and give every other username a message that names the field. The starter is the welcome application, deployed as /greeting. The sample checks open it with a new session for each username, type it, press Enter and read the answer, as a browser would:

The starter page and the sample checks

XHTML · exercise/greeting/src/main/webapp/welcome.xhtml

<!DOCTYPE html>
<!-- The exercise: accept a username of 3 to 20 letters, digits or underscores, and give every other username a
     message. The rules go inside h:inputText, as the lesson's steps describe; the bean needs no change. -->
<html lang="en" xmlns:h="jakarta.faces.html" xmlns:f="jakarta.faces.core">
<h:head>
  <title>Welcome</title>
</h:head>
<h:body>
  <h1>Welcome</h1>
  <h:form id="login">
    <h:outputLabel for="username" value="Username"/>
    <h:inputText id="username" label="Username" value="#{visitor.username}" required="true">
      <!-- Your rules for the length and the characters go here. -->
    </h:inputText>
    <h:commandButton id="enter" value="Enter" action="#{visitor.greet}"/>
  </h:form>
  <h:messages id="messages"/>
  <p id="greeting">#{visitor.greeting}</p>
</h:body>
</html>

Java · exercise/Checks.java

// Sample checks for the exercise of the lesson "Jakarta Faces 4.1: a first Facelets app". Run them while the exercise
// application is deployed:
//
//   java Checks.java http://127.0.0.1:28642/greeting/welcome.xhtml
//
// Each check opens the page with a new session, types a username, presses Enter and reads the answer, as a browser
// would. The exit status is 0 only when every check passes.

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;

static final Pattern VIEW_STATE = Pattern.compile("name=\"jakarta\\.faces\\.ViewState\"[^>]*value=\"([^\"]*)\"");
static final Pattern GREETING = Pattern.compile("<p id=\"greeting\">(.*?)</p>");
static final Pattern MESSAGE = Pattern.compile("<li>\\s*(.*?)\\s*</li>");

/** What the page answered to one username. */
record Answer(String greeting, List<String> messages) {}

int checks = 0;
int passed = 0;

void main(String[] args) throws Exception {
    URI page = URI.create(args[0]);

    String html = HttpClient.newHttpClient().send(HttpRequest.newBuilder(page).build(), BodyHandlers.ofString()).body();
    check("the page has the field login:username and a view state",
            html.contains("name=\"login:username\"") && VIEW_STATE.matcher(html).find(), "one of them is missing");

    welcomed(page, "asha_p", "asha_p");
    welcomed(page, "abc", "abc (3 characters)");
    welcomed(page, "a".repeat(20), "a username of 20 characters");
    rejected(page, "", "an empty username");
    rejected(page, "ab", "ab (2 characters)");
    rejected(page, "a".repeat(21), "a username of 21 characters");
    rejected(page, "asha p!", "\"asha p!\"");

    IO.println(passed + " of " + checks + " checks passed");
    if (passed < checks) System.exit(1);
}

void welcomed(URI page, String username, String what) throws Exception {
    Answer a = enter(page, username);
    check(what + " is welcomed", a.greeting().equals("Welcome " + username) && a.messages().isEmpty(), said(a));
}

void rejected(URI page, String username, String what) throws Exception {
    Answer a = enter(page, username);
    check(what + " gets a message that names the field", a.greeting().isEmpty() && a.messages().stream().anyMatch(m -> m.startsWith("Username:")), said(a));
}

String said(Answer a) {
    return a.messages().isEmpty() ? "the page said \"" + a.greeting() + "\"" : "the page said " + a.messages();
}

/** Loads the page, fills in the username and posts the form back with the page's view state. */
Answer enter(URI page, String username) throws Exception {
    HttpClient browser = HttpClient.newBuilder().cookieHandler(new CookieManager()).build();
    String html = browser.send(HttpRequest.newBuilder(page).build(), BodyHandlers.ofString()).body();
    Matcher state = VIEW_STATE.matcher(html);
    if (!state.find()) throw new IllegalStateException("the page has no jakarta.faces.ViewState field");
    String form = "login=login&login:username=" + URLEncoder.encode(username, StandardCharsets.UTF_8)
            + "&login:enter=Enter&jakarta.faces.ViewState=" + URLEncoder.encode(state.group(1), StandardCharsets.UTF_8);
    HttpRequest post = HttpRequest.newBuilder(page)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString(form))
            .build();
    String reply = browser.send(post, BodyHandlers.ofString()).body();
    Matcher greeting = GREETING.matcher(reply);
    List<String> messages = MESSAGE.matcher(reply).results().map(m -> m.group(1)).toList();
    return new Answer(greeting.find() ? greeting.group(1) : "", messages);
}

void check(String what, boolean ok, String otherwise) {
    checks++;
    if (ok) passed++;
    IO.println((ok ? "PASS  " : "FAIL  ") + what + (ok ? "" : ": " + otherwise));
}
The sample checks against the starter Python · exercise_session.py
from session import run

# The exercise: builds the starter application, deploys it as /greeting on 127.0.0.1:28642 and runs the sample checks
# against it. The starter accepts every username that is not empty, so the checks for the rules still to be written
# fail; once they are in welcome.xhtml, the same commands print "8 of 8 checks passed".
run(
    """
cd exercise/greeting
mvn -q package
cd ../..
sh new-base.sh base 28642
cp exercise/greeting/target/greeting.war base/webapps/
$CATALINA_HOME/bin/startup.sh
java exercise/Checks.java http://127.0.0.1:28642/greeting/welcome.xhtml
$CATALINA_HOME/bin/shutdown.sh
""",
    fails=("java exercise/Checks.java http://127.0.0.1:28642/greeting/welcome.xhtml",),
    port=28642,
)

Output

# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0 · Apache Tomcat 11.0.26
$ cd exercise/greeting
$ mvn -q package
$ cd ../..
$ sh new-base.sh base 28642
$ cp exercise/greeting/target/greeting.war base/webapps/
$ $CATALINA_HOME/bin/startup.sh
$ java exercise/Checks.java http://127.0.0.1:28642/greeting/welcome.xhtml
PASS  the page has the field login:username and a view state
PASS  asha_p is welcomed
PASS  abc (3 characters) is welcomed
PASS  a username of 20 characters is welcomed
PASS  an empty username gets a message that names the field
FAIL  ab (2 characters) gets a message that names the field: the page said "Welcome ab"
FAIL  a username of 21 characters gets a message that names the field: the page said "Welcome aaaaaaaaaaaaaaaaaaaaa"
FAIL  "asha p!" gets a message that names the field: the page said "Welcome asha p!"
5 of 8 checks passed
[exit status 1]
$ $CATALINA_HOME/bin/shutdown.sh

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

The starter rejects only an empty field. Edit exercise/greeting/src/main/webapp/welcome.xhtml until the same commands print 8 of 8 checks passed:

  1. Inside h:inputText, add f:validateLength with minimum="3" and maximum="20". The page already declares the prefix f for jakarta.faces.core.
  2. Add f:validateRegex with a pattern that allows only letters, digits and underscores.
  3. Run the checks again and read what the check for “asha p!” prints: the pattern rule’s own message does not name the field. Give h:inputText a validatorMessage that does, such as “Username: use 3 to 20 letters, digits or underscores”. It replaces the message of every validator of that field.
Regex Tester Try your pattern on asha_p, ab and asha p! first. Wrap it in ^ and $ in the tester, because f:validateRegex compares the whole value.

Interview questions

Fresher · Why does a Faces application deployed on Tomcat carry Mojarra and Weld in its WAR?

Tomcat is a servlet container, not a full Jakarta EE server. Tomcat 11 implements Servlet 6.1, Pages 4.0, Expression Language 6.0, WebSocket 2.2 and Authentication 3.1, but no Faces and no CDI. A Faces application needs both: an implementation such as Mojarra 4.1 to run the request life cycle and render the components, and a CDI container, because since Faces 4.0 the beans a page uses are CDI beans and several objects that expressions use come from CDI. On Tomcat the WAR therefore carries jakarta.faces and weld-servlet-shaded in WEB-INF/lib, and a beans.xml so that Weld looks at the application’s classes. A full server such as GlassFish 8 ships both, and the WAR then contains neither.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 The welcome WAR carries jakarta.faces-4.1.18.jar and weld-servlet-shaded-6.0.4.Final.jar in WEB-INF/lib. Why does an application for Tomcat 11 need them?

    Choose one answer.

    Show the answer to question 1

    Answer: Tomcat 11 implements neither Faces nor CDI, so the application brings an implementation of each

    Tomcat 11 implements Servlet 6.1, Pages 4.0, Expression Language 6.0, WebSocket 2.2 and Authentication 3.1. Faces and CDI come from the WAR: Mojarra runs the pages, and Weld creates the beans they use. A full Jakarta EE server ships both, and there they would be marked provided.

  2. Question 2 of 5 A page renders with an empty field, but the first post answers HTTP 500 with "Target Unreachable, identifier [visitor] resolved to null", and Tomcat's log holds WELD-ENV-000028. What is the most likely cause?

    Choose one answer.

    Show the answer to question 2

    Answer: The WAR has no WEB-INF/beans.xml, so Weld found no bean archive and no bean named visitor exists

    WELD-ENV-000028 says that Weld skipped its start because it found no bean archive. Weld on Tomcat scans only archives with a beans.xml. Rendering survived because an unknown name evaluates to null, which renders as an empty field; storing the posted value needs a real object, so the post failed.

  3. Question 3 of 5 Which values does the browser send back with a post of the welcome form?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • jakarta.faces.ViewState, which identifies the saved view
    • login=login, which names the form that was posted
    • login:enter=Enter, the button that was pressed

    A Faces form posts its own id, every field by its client id (login:username), the name of the button that was pressed and the jakarta.faces.ViewState field. The bean's values stay on the server, and the label is part of the saved view, not of the post.

  4. Question 4 of 5 A bean from a JSF 2 book is annotated @ManagedBean and @RequestScoped from javax.faces.bean. What makes it a bean a Faces 4.1 page can use?

    Choose one answer.

    Show the answer to question 4

    Answer: @Named from jakarta.inject and @RequestScoped from jakarta.enterprise.context, with a beans.xml

    Faces 4.0 removed JSF's own managed beans, so jakarta.faces.bean does not exist and javac reports it, as the recorded session shows. Beans are CDI beans: @Named gives the name the page uses, and a CDI scope annotation makes Weld discover the class.

  5. Question 5 of 5 Without a servlet mapping of its own, which addresses serve the page welcome.xhtml of an application deployed as /welcome?

    Choose one answer.

    Show the answer to question 5

    Answer: /welcome/welcome.xhtml, /welcome/welcome.jsf, /welcome/welcome.faces and /welcome/faces/welcome.xhtml

    Faces maps itself to .xhtml, .jsf, .faces and /faces/ when an application declares no mapping, so the same page answers at four addresses. The lesson's web.xml maps *.xhtml only, and the recorded session shows the other three answering 404.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A Faces page on Tomcat renders normally, but the first post answers HTTP 500 with "Target Unreachable, identifier [visitor] resolved to null". How do you find the cause, and why did the page itself load without an error?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    You inherit a JSF 2.2 application: javax.faces imports, @ManagedBean beans, pages with xmlns.jcp.org namespaces and a FacesServlet mapped to *.jsf. It must move to Faces 4.1 on Tomcat 11. What breaks, in which order do you fix it, and how do you prove the move worked?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Faces builds each page as a tree of server-side components from a Facelets file. Expressions bind the components to CDI beans, and the view state carries the form from one request to the next.
  • Tomcat 11 has no Faces and no CDI: the WAR carries Mojarra 4.1 and Weld 6.0 (weld-servlet-shaded), and a beans.xml so that Weld looks at the application.
  • @Named with a CDI scope such as @RequestScoped replaces @ManagedBean, and the jakarta.faces.html and jakarta.faces.core names replace the xmlns.jcp.org namespaces.
  • Map the FacesServlet to *.xhtml only, and set jakarta.faces.FACELETS_SKIP_COMMENTS so that page comments stay on the server.
  • Target Unreachable, identifier [visitor] resolved to null means that no bean has that name: check beans.xml, the scope annotation and the spelling.
  • When validation fails, the post stops before the model is updated: the page comes back with the message, and the action does not run.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.