Jakarta Faces
Jakarta Faces (formerly JSF) is a technology for server-side HTML pages built from components. A page is described in an XHTML file, data and actions come from a bean, and the server builds the HTML, accepts forms and validates fields. New interfaces are more often built in JavaScript on top of a REST API today, but many enterprise systems are written in Faces and still need maintenance. This lesson introduces the basics.
Enabling Faces
Faces is enabled with the @FacesConfig annotation on any bean of the application:
// src/main/java/com/example/blog/FacesActivator.java
package com.example.blog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.annotation.FacesConfig;
@FacesConfig
@ApplicationScoped
public class FacesActivator {
}The page bean
A page needs an object to take data from. The @Named annotation makes the bean available on the page under the name postsPage — the class name starting with a lowercase letter:
// src/main/java/com/example/blog/PostsPage.java
package com.example.blog;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
@Named
@RequestScoped
public class PostsPage {
@Inject
PostRepository repository;
@Inject
PostService service;
private String title;
private String body;
public List<Post> getPosts() {
return repository.findAll();
}
public String publish() {
service.publish(new NewPost(title, body));
return "posts?faces-redirect=true";
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
}Form fields are bound to a getter–setter pair. The publish method is called by a button and returns where to go next: faces-redirect=true asks the browser to load the page again so that a refresh does not submit the form twice.
The page
Create the file src/main/webapp/posts.xhtml:
<!-- src/main/webapp/posts.xhtml -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets">
<h:head>
<title>Blog posts</title>
</h:head>
<h:body>
<h1>Blog posts</h1>
<ul>
<ui:repeat value="#{postsPage.posts}" var="post">
<li>#{post.title} (#{post.createdAt})</li>
</ui:repeat>
</ul>
<h2>New post</h2>
<h:form id="postForm">
<h:outputLabel for="title" value="Title" />
<h:inputText id="title" value="#{postsPage.title}" required="true" />
<h:outputLabel for="body" value="Text" />
<h:inputTextarea id="body" value="#{postsPage.body}" />
<h:commandButton value="Publish" action="#{postsPage.publish}" />
<h:messages />
</h:form>
</h:body>
</html>#{...} expressions are the Expression Language: #{postsPage.posts} calls getPosts(), #{postsPage.publish} calls the publish method. The h: and ui: prefixes bring in component libraries.
The result
Open http://localhost:8080/blog/posts.xhtml. The server returns plain HTML — a list and a form:
<ul>
<li>Hello, Jakarta EE (2026-10-01)</li>
<li>Servlets and filters (2026-10-02)</li>
</ul>
<h2>New post</h2><form id="postForm" name="postForm" method="post" action="/blog/posts.xhtml" ...>Fill in the form and press "Publish" — the post is saved through the same PostService as in the REST API and shows up in the list. If you leave the title empty, required="true" stops the form, and h:messages shows postForm:title: Validation Error: Value is required.
Main components
| Component | What it renders |
|---|---|
h:form | A form |
h:inputText, h:inputTextarea | An input field and a multi-line field |
h:commandButton | A button that calls a bean method |
h:outputText | Text with HTML escaping |
h:dataTable | A table from a list |
h:messages | Error messages |
ui:repeat | Repeats markup for each element |
Common mistake
Creating an .xhtml page but not enabling Faces. Without @FacesConfig (or a WEB-INF/faces-config.xml file) the server serves the file as is, and the browser gets the source text with h:body tags and #{postsPage.posts} expressions instead of the list of posts.