JSF tutorial for Java developers

Jakarta Server Faces 3 is the current specification for JSF which runs on Jakarta EE containers such as WildFly application server. In this JSF tutorial we will cover the main features of JSF which are coming from the earlier JSF releases.
Please note: if you are moving to Jakarta EE 9, Jakarta Faces 3.0 has a breaking change due to the namespace change from javax.faces to jakarta.faces.

JSF Managed Beans Annotations

Update: Please note that ManagedBeans are deprecated since JSF 2.3. Check at the end of this article "From ManagedBeans to CDI Beans for more details"

Let’s start. One of the most useful additions of JSF 2.0 is the ability to add annotations for your Beans instead of using the file faces-config.xml. You might think there it’s going on a religious war against XML configuration files- it’s not so.

Actually using annotations simplifies the management of your projects because you don’t need to lock/share a file which is used by all your developers, every time you need to add a bean or a navigation rule.Let’s show an example:

package sample;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean(name="person", eager="true")
@RequestScoped

public class PersonBean {

    String personName;
    String prompt;
    String email;
    int amount;
    
     public PersonBean() {
        this.prompt="Enter your name please";
    }

    public int getAmount() {
        return amount;
    }
    public void setAmount(int amount) {
        this.amount = amount;
    }
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }


    public String getPrompt() {
        return prompt;
    }

    public void setPrompt(String prompt) {
        this.prompt = prompt;
    }


    public String getPersonName() {
        return personName;
    }

    public void setPersonName(String name) {
        personName = name;
    }
    public void doSomething() {
        personName= personName.toUpperCase();
    }
     
}

In this example we have tagged our Bean as ManagedBean by using the annotation @ManagedBean.
We have set the attribute name to define how we will reference the bean in our Jsp pages and the attribute eager = true, which means that the Bean will be instantiated at application startup and not when it’s first referenced.
If you don’t specify the name attribute, the Bean will be bound using the Class name, with the first letter in lowercase. If you don’t specify the eager attribute it will default to false.
The other annotation we have added is @RequestScoped which means the Bean is bound in the Request.
All possible alternatives are:

  • @NoneScoped
  • @RequestScoped
  • @ViewScoped
  • @SessionScoped
  • @ApplicationScoped
  • @CustomScope

What is the View Scope ? View Scope has been introduced in JSF 2.0 and it’s particularly useful when you are editing some objects while staying in the same page. In other words it’s something broader then request but smaller then session, since this scope terminates if you navigate to other pages.

Here is a sample which shows how to create a CRUD applications using JSF and @ViewScoped: JSF ViewScoped tutorial

Now Let’s add a basic JSP page which will be used in our example:

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"       xmlns:f="http://java.sun.com/jsf/core"       xmlns:h="http://java.sun.com/jsf/html">
<h:body>
   <f:view>   
      
     <h:form id="helloForm">
     <h:outputText value="#{person.prompt}" />
          
     <h:inputText id="name" value="#{person.personName}" />
     <h:commandButton value="go" action="#{person.doSomething}" />

     </h:form>
   </f:view>
 </h:body>
</html>

What happened to JSP Tags?

One thing you might have noticed is the absence of standard JSP tags. As a matter of fact, JSP technology is considered to be a deprecated presentation technology for Java Server Faces 2.0. That simply means, that JSF preferred view technology is Facelets, for an introduction to Facelets have a look at this tutorial.

By running this trivial example, the command button will trigger the action doSomething which will uppercase the attribute. Let’s complete the example adding a basic web.xml and faces.config.

Here’s the web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <context-param>
        <param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
        <param-value>true</param-value>
    </context-param>

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
</web-app>

And the faces-config.xml just needs:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" 
          version="2.0"> 
</faces-config>

Notice the attribute version=”2.0″. If you use by mistake a faces-config.xml with the attribute set to “1.2” or “1.0”, the code will run with the earlier JSF release.


Adding validation to your pages

You now have several options to validate your components. If you need a simple validation rule, you can use the basic f:validate options like in this example:

<h:inputText id="amount" value="#{person.amount}" > 
   <f:validateLength maximum="10" /> 
</h:inputText>

If you need a more complex rule, you can use the Bean validation:

 <h:inputText id="email" value="#{person.email}"
      validator="#{personBean.validateEmail}" >
</h:inputText>
public void validateEmail(FacesContext context, UIComponent validated,Object value) { 
   String mail = (String) value; if (!mail.matches(".+\\@.+\\..+")) { 
      FacesMessage msg = new FacesMessage("This is not an e-mail!"); 
      context.addMessage(validated.getClientId(context),msg); 
      throw new ValidatorException(msg); 
    } 
}

Better and simpler navigation

Another add-on in JSF 2.0, is that now supports implicit navigation, which can now be addresses directly in the JSP file, like in this example:

<h:commandButton value="Press me!" action="nextPage"/>

This will cause the JSF engine to invoke a page named nextPage in the same directory of the current page.
In our example, nextPage.jsp will be invoked. You can also specify full page name, if you have multiple pages with the same name in the same directory.

<h:commandButton value="Press me!" action="nextPage.xhtml"/>

If the page where we need to navigate is located in another directory, you can obviously use the standard path convention:

<h:commandButton value="Press me!" action="/subdir/anotherPagel"/>

You might think that the file faces-config.xml is pretty useless at this point. It’s not true, as a matter of fact an important add-on in JSF 2.0 is the ability to perform conditional navigation. By using conditional navigation you can get rid of navigations detail from your JSF Beans, where you will concentrate exclusively on the model that the Bean incarnates.Let’s see a practical example:

<navigation-rule>
        <from-view-id>/home.jsp</from-view-id>
        <navigation-case>
            <from-action>#{person.action}</from-action>
            <if>#{person.validationOk}</if>
            <to-view-id>/done.jsp</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-action>#{person.action}</from-action>
            <if>#{!person.validationOk}</if>
            <to-view-id>/error.jsp</to-view-id>
        </navigation-case>
</navigation-rule>
public void action() { 
   if (name != null && name.trim().length() > 0) validationOk = true; else validationOk = false; 
}

In this example, instead of returning the next view from the method action() of the Class Person, we just set the property validationOk. Then the configuration file will decide where to navigate depending on the value of the property.

Finally, another opportunity offered by JSF 2 is the ability to issue a redirect when you invoke an action. The default action before now was to issue a forward, which is surely more efficient but sometimes you might need to redirect the navigation to a new page.
A typical example is where you have issues releated to the Refresh browser button, which might cause problems to your application if the address on the navigation bar is not consistent with the actual state of the application.

With JSF 2 you can just add the parameter faces-redirect=true to enable page redirection.

<h:commandLink  action="/sample?faces-redirect=true" value="Click"/>

From ManagedBeans to CDI Beans

As per JSF 2.3, @ManagedBean is deprecated. This means that there’s not anymore a reason to choose @ManagedBean over @Named.

Managed Beans vs CDI Beans

A @ManagedBean is managed by JSF framework and is only via @ManagedProperty available to another JSF managed beans.

A @Named Bean is managed by application server via CDI framework and is via @Inject available to any kind of a container managed artifact like @WebListener, @WebFilter, @WebServlet, @Path, @Stateless.
On the other hand, @ManagedProperty does not work inside a @Named or any other container managed artifact. It works really only inside @ManagedBean.

Another difference is that CDI actually injects proxies (like EJBs) delegating to the current instance in the target scope on a per-request/thread basis. This mechanism allows injecting a CDI bean of a narrower scope in a bean of a broader scope, which isn’t possible with JSF @ManagedProperty. JSF “injects” here the physical instance directly by invoking a setter.

The roadmap to move from ManagedBeans to CDI requires the following steps:

  • Add a empty beans.xml file in WEB-INF.
  • Change all JSF @ManagedBean to CDI @Named annotations.
  • Replace all JSF scope annotations with CDI or OmniFaces scope annotations.
  • Change all JSF @ManagedProperty with CDI @Inject annotations.