A Quick Look At Faces (JSF) 4.0 In Jakarta EE 10

Photo of Luqman Saeed by Luqman Saeed

Jakarta EE 10 shipped with the fourth major release of its component based web framework Jakarta Faces. Hitherto known as Jakarta Server Faces, and Java Server Faces before that, Jakarta Faces, or just Faces,version 4.0is the first major version with API change since version 2.3 in Java EE 8. Among the major changes in this version are:

There were also some pruning of the API such as 

As you can see, there was a lot of changes. In this blog post, we take a look at the programmatic API, extensionless mapping, @ClientWindowScoped and multi-file upload.

Programmatic View API In Faces 4

The only way to create views in previous versions of Faces was through .xhtml files. With Faces 4, a new Java API is available for creating views, without the need for .xhtml files. A programmatic view is a Java class that extends jakarta.faces.view.facelets.Facelet, annotated with the new jakarta.faces.annotation.View CDI qualifier, passing in the view ID pattern. The code below shows a Greet.java Facelet that is mapped to the /greet.xhtml path.

public class Greet extends Facelet {

public void apply(FacesContext facesContext, UIComponent root) throws IOException {
if (!facesContext.getAttributes().containsKey(IS_BUILDING_INITIAL_STATE)) {

var components = new ComponentBuilder(facesContext);
var rootChildren = root.getChildren();

var doctype = new UIOutput();
doctype.setValue("<!DOCTYPE html>");

var htmlTag = new UIOutput();
htmlTag.setValue("<html xmlns=\"http://www.w3.org/1999/xhtml\">");

HtmlBody body = components.create(HtmlBody.COMPONENT_TYPE);

HtmlForm form = components.create(HtmlForm.COMPONENT_TYPE);

HtmlOutputText message = components.create(HtmlOutputText.COMPONENT_TYPE);

HtmlCommandButton actionButton = components.create(HtmlCommandButton.COMPONENT_TYPE);
e -> message.setValue("Hello, World! Welcome to Faces 4.0 on Jakarta EE 10"));



htmlTag = new UIOutput();

private static class ComponentBuilder {
FacesContext facesContext;

ComponentBuilder(FacesContext facesContext) {
this.facesContext = facesContext;

<T> T create(String componentType) {
return (T) facesContext.getApplication().createComponent(facesContext, componentType, null);

The class is annotated @View("/greet.xhtml"), meaning the created component should be hosted/accessible at that path.  The apply method creates an HTML doctype, literal html element, a form, an output text and a button. The button has a click listener that sets the message value on the output text. 

The Faces Java API is very similar to any Java GUI building API like JavaFX, Swing and Vaadin. All the Faces components used in Greet.java have Java methods for setting the various values and properties you would if you had created a .xhtml Facelets file. Accessing this UI at the pathhttp://localhost:8080/jakarta-faces/greet.xhtml renders the following.

Screenshot_20230110_125156Clicking the Greet button renders the message passed in the click listener in the code as shown below.

Screenshot_20230110_125324The Faces Java API is very new, and has some missing functionality. However, for developers that prefer to create their application views in Java, this API is a welcome development. Whether it will be developed further is going to be a matter of how many people adopt this API and give feedback. 

Extensionless Mapping

Hitherto, accessing a view in the browser meant having to add the .xhtml file extension to the view. So for instance, to access the view index.xhtml, you needed to add the literal .xhtml in the browser. With Faces 4.0, you can now enable extensionless mapping and have /index automatically resolved to /index.xhtml by the FacesServlet as shown below. The first image shows the extensionless mapping to /index.


The same url can be accessed in the old way still via /index.xhtml as shown below.

Screenshot_20230110_130327This extensionless mapping feature can be enabled in the web.xml file via context param as shown below.


In my test run, I found that extensionless mapping didn't seem to work with programmatic UIs. This is something you should test for yourself and keep in mind. 


Faces 4.0 comes with the @ClientWindowScoped bean scope that is based on the jakarta.faces.lifecycle.ClientWindow. This scope keeps a backing/managed bean alive as long as the auto-generated jfwid query parameter value as shown in the image below is not changed, or the same URL isn't opened in another browser window/tab. As long as the generated value is the same, the bean instance will be kept across different page navigations.


This feature can be enabled through the context param as shown below.


The number of client window scoped bean instances per HTTP session can be set through the context param as shown below.


Multiple File Upload

The  <h:inputFile/> has a new multiple attribute that when set to true, allows the upload of multiple files. Another new attribute, accept, helps to restrict the file types that the browser will display/allow to select. The file-upload.xhmtl file below shows a simple example of multi-file upload using these new attributes.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html>
<html lang="en" xmlns:h="jakarta.faces.html">
    <title>Upload files</title>
    <h2>Faces multi-file upload</h2>
    <h:form id="uploadPanel" enctype="multipart/form-data">
        <h:panelGrid columns="2" styleClass="default">
            <h:inputFile  id="inputFiles"  multiple="true" value="#{fileUploadBean.files}" accept="image/jpeg,image/png,image/gif" required="true"/>
            <h:commandButton value="Upload"

The above facelet file renders as shown below.

Screenshot_20230110_135104The backing bean of this page is shown below. Faces will conveniently skip empty files, that is files with empty name or zero file size. You still will have to however, validate that the file types selected meet business requirements.

public class FileUploadBean {
    private List<Part> files;
    private static Logger logger = Logger.getLogger(FileUploadBean.class.getName());

    public String uploadFile() {
        for (final var part : files) {

            //Process files, carry out custom validations etc etc.
            logger.log(Level.INFO, () -> String.format("File name: %s, Content-type: %s, File size: %d", part.getSubmittedFileName(),
                    part.getContentType(), part.getSize()));
        return "upload-success.xhtml?faces-redirect=true";


The files list will contain the uploaded files for the bean to validate and consequently upload/store in the database or however the business case demands. 


As you can see, Faces 4.0 has a number of major and quality of life features that makes this venerable framework even more productive. Despite the meteoric rise of "heavy clients" in the form of JavaScript/TypeScript frontend frameworks, server side web application frameworks like Jakarta Faces are still important for many enterprises that need a lower learning curve and much more easy to pick up UI development platform. Jakarta Faces is a great choice especially as a Java developer when you need a UI framework for your application. 

Want to find out more about the changes of Jakarta EE 10? Join our webinar, Friday 27th, 3.00PM GMT, 'What's New in Jakarta EE 10: What Got Removed or Deprecated?'Part 3

You can also explore our previous content:

Related Posts