JSF Best Practice

I do developing with Java Server Pages (JSF) since the early beginning with version 1.0. Today , 17 years later,  we have version 2.3 and thus a lot of improvements and new features.  I’ve made many mistakes in using this standard web framework in my own applications. And therefore I would like to show some best practice rules in this Blog. And I hope this will simplify the work with JSF – for you and also for me – during refactoring my own applications. Most of which I write here comes from the great book ‘ The Definitive Guide to JSF in Java EE 8’ written by Bauke Scholtz (BalusC) and Arjan Tijms. I recommend you to buy this book if you have had already made experience with JSF.  So let’s start…

CDI Beans and Scopes

One of the most important topics in designing a good JSF application are the scops of Backing Beans. Since version 2.0 we can use CDI and it is strongly recommended to use this concept instead of the deprecated BackingBean concept.  A BackingBean is a sole JavaBean which is used in the JSF pages providing functions and also data. Scope is important here because it defines how your data is shared.

@ApplicationScoped

An application-scoped managed bean instance is tied to the lifetime of the web application itself. There’s only one instance of an application-scoped managed bean throughout your web application. It is not the same but similar to a Singleton EJB.

@SessionScoped

A session-scoped bean is stored in the user session. In the early beginning it was the only type to store data over several front-end interactions of a single user.  It is easy to understand, but bears also some threats in relation to the behavior of your application in the browser.

@ConversationScoped

The conversation scope can be controlled by the application itself by calling the begin() and end() methods of the Conversation instance. Therefore this scope is the most flexible scope.

@FlowScoped

A flow-scoped managed bean is tied to the lifetime of a JSF flow. It is similar to the conversation scope but controlled internally by the concept of JSF pages contained by one sub-folder. So as long as the application did call pages within the same sub directory a flowScoped bean will hold its status.

@ViewScoped

ViewScoped means that the bean is tied to a single JSF page. So this scope fits perfect for single pages with ajax functionality.

@RequestScoped

A RequestScoped bean is only tied to the lifetime of a single  HTTP
request, which is for JSF the most important case. This scope is similar to the behavior of a stateless session  EJB

Which scope in which situation?

So the important question is – which scope to choose for which situation? Here are the rules of thumb by  BallusC and Arjan Tijms:

Rule-1: Start with a @RequestScoped bean. Once you notice that you loose state or business data than choose the next higher scope level.

Rule-2: Avoid @ApplicationScope if you do not want that state or data will be shared among all users.

Rule-3: Avoid @SessionScope if you do not want that the state or data willbe shared among all browser tabs in the same user session. This will often worsen the end user experience when the user is switching between tabs (and they well do this!)

Rule-4: Avoid @RequestScope if you don’t want lose state or data over several http requests. This is typically the situation when you are using Ajax. For this case @RequestScope will causing non-working forms.

Rule-5: The use of @ViewScoped, @FlowScoped, or @ConversationScoped beans for request, session – or application-scoped data, or the use of @SessionScoped beans for application-wide data has no effect for the end user but in turn it causes unnecessary memory consumption within your application.

Naming Conventions and How to Cut Your CDI Beans

If possible, separate data and function. Functions and business logic can easily covered by a @RequestScoped CDI bean. This corresponds to the concept of stateless session EJBs. The suffix ‘controller’ is a good practice for naming .

@Named @RequestScoped 
public class ProductController {
    @Inject
    ProductData productData;
    public String showProduct() {
        ....
        return "/next?faces-redirect=true";
    }
    public Product getProduct() {
        return productData.getData();
    }
}

Data have always a state. Therefore the @RequestScope is unsuitable for state and data. Data can be stored by CID beans covering the concrete business case.  If you show/edit the data in a single page use a @ViewScoped CDI Bean. If you show/edit the data over several pages (e.g. shopping cart) use a @ConversationScope or @FlowScoped CID Bean. Try to avoid using a @SessionScoped bean.

@Named @ViewScoped 
public class ProductData {
  private Product product = new Product();

  public Product getData() {
    return pproduct;
  }
}

How to Handle a List of Data?

Another important question is how to deal with a list of data (e.g. from a sql database). There are some simple rules to care about:

Rule-1:  use a RequestScoped Bean instead of wider scopes if possible

Rule-2 : use a @PostConstruct annotated method to compute the result-set once

Rule-3:  provide the result set in a value property

Rule-4: avoid to call business logic in a getter method of a jsf backing bean.

@Named
@RequestScoped
public class Bean {

    private List<Item> items;
    @EJB
    private ItemService itemService;
    @PostConstruct
    public void init() {
        items = itemService.list();
    }
    public List<Item> getItems() {
        return items;
    }
}

See also this discussion on stackoverflow.

Pagination and Filtering in a List of Data

If you need a pagination or filter mechanism in your data set you have the problem of how to store the current page or filter rules? In this case use a ConversationScoped or ViewScoped bean only to threat with the navigation data (like the page index or the current filter). The user can change the navigation data within  a search page. To provide the RequestScoped view bean with this information you can use the f:viewAction tag:

<f:metadata>
   <f:viewAction action="#{viewController.setQuery('type:team')}" />
   <f:viewAction action="#{viewHandler.loadData(viewController)}" />
</f:metadata>
<f:view>
    <h:dataTable value="#{viewHandler.data}" var="record">
     ....
</f:view>

In this example the viewController bean is ConversationScoped and the viewHandler bean is RequestScoped. In the f:metadata tag the navigator is initialized and the method loadData() in the RequestScoped bean just called once.

@Named
@RequestScoped
public class ViewHandler implements Serializable {
  private static final long serialVersionUID = 1L;
  private List<MyData> data = null;

  @EJB
  DataService dataService;

  public void loadData(ViewController viewController) {
    String query = viewController.getQuery();
    // load data
    ...
  }

  public List<MyData> getData() {
    return data;
  }
}

Instead of a f:viewAction you can use also a preRenderView event:

<f:metadata>
  <f:event type="preRenderView" listener="#{viewController.setQuery('type:team')}"/>
  <f:event type="preRenderView" listener="#{viewHandler.loadData(viewController)}"/>
</f:metadata>

The behavior is the same.

@ConversationScoped

In the example before I showed you how to use the @RequestScope to display result sets of data. If you need to show and edit a single data record a @ConversationScoped bean is the best choice. In a @ConversationScoped you can control how long the data record is hold in a the session. With this scope you can develop complex page flows but also singe page applications with ajax support.

Here you can see an example of a @ConversationScoped bean to hold a single data record. I will explain how it works:

public abstract class MyDataController implements Serializable {

  private static final long serialVersionUID = 1L;
  @Inject
  Conversation conversation;
  MyData data = null;
  @EJB
  MyDataService dataService;
  String defaultActionResult;
  
  public MyData getData() {
    return data;
  }

  /**
  * This method extracts an id from the query param 'id' and loads the
  * data. After the data was loaded, a new conversation is started.
  * <p>
  * The method is not running during a JSF Postback or in case of a JSF
  * validation error.
  */
  public void onLoad() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    if (!facesContext.isPostback() && !facesContext.isValidationFailed()) {
      // extract the id from the query string
      FacesContext fc = FacesContext.getCurrentInstance();
      Map<String, String> paramMap = fc.getExternalContext().getRequestParameterMap();
      String id = paramMap.get("id");
      setDefaultActionResult(facesContext.getViewRoot().getViewId());
      load(id);
    }
  }

  /**
  * Loads a data by a given id and starts a new conversaton. The
  * conversaion will be ended after the data was processed or after the
  * MaxInactiveInterval from the session.
  * 
  * @param id
  */
  public void load(String id) {
    if (id != null && !id.isEmpty()) {
      data = dataService.load(id);
      startConversation();
    }
  }

  public String getDefaultActionResult() {
    if (defaultActionResult == null) {
      defaultActionResult = "";
    }
    return defaultActionResult;
  }

  public void setDefaultActionResult(String defaultActionResult) {
    this.defaultActionResult = defaultActionResult;
  }

  /**
  * Closes the current conversation and reset the data item. A conversation is
  * automatically started by the methods load() and onLoad(). You can call the
  * close() method in a actionListener on any JSF navigation action.
  */
  public void close() {
    if (!conversation.isTransient()) {
      conversation.end();
    }
  }

  /**
  * Starts a new conversation
  */
  protected void startConversation() {
    if (conversation.isTransient()) {
      conversation.setTimeout(
           ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest())
           .getSession().getMaxInactiveInterval() * 1000);
        conversation.begin();
      }
    }
  }
}

This example of a data controller  is a @ConversationScoped CDI bean to control the life cycle of a data record in an JSF application. The bean can be used in single page applications, as well for complex page flows. The controller is easy to use and supports bookmarkable URLs.

To load a data record the methods load(id) and onLoad() can be used. The method load expects the id of a record to be loaded. The onLoad() method extracts the id automatically from the query parameter ‘id’. This is the recommended way to support bookmarkable URLs. To load the data the onLoad method can be triggered by an jsf viewAction placed in the header of a JSF page:

<f:metadata>
   <f:viewAction action="#{myDataController.onLoad()}" />
</f:metadata> }

A bookmarkable URL looks like this:

   /myForm.xthml?id=[ID]

In combination with the viewAction the DataController is automatically
initialized. After the data was loaded, a new conversation is started.

After you updated the data you can call the close() method so that the conversation is automatically closed. Stale conversations will automatically timeout with the default session timeout.

The DefaultAction is extracted during the onLoad() method and can be used after a business method to start a Post-Redirect-Get. This guarantees
bookmarkable URLs.

public String myBusinessMethod() {
  ...
  return getDeafultAction()+"&faces-redirect=true";
}

Events

CDI Events are not specific for JSF but can be used to easily connect different JSF Controllers together.  This is also called the Observer-Pattern.

First you define a application specific event type:

public class DataEvent {

  public static final int DATA_CREATED = 1;
  public static final int DATA_UPATEDD = 2;
  ...

  private int eventType;
  private MyData data;

  public DataEvent(MyData data, int eventType) {
    this.eventType = eventType;
    this.data = data;
  }

  public int getEventType() {
    return eventType;
  }

  public Data getData() {
    return data;
  }
}

To fire an event in your CDI controller inject the event

  @Inject
  protected Event<WorkflowEvent> events;
 
  ...  
    events.fire(new DataEvent(data, DataEvent.DATA_CREATED));
  ...

Now you can observe this event from any CDI Controller within your application:

public void onDataEvent(@Observes DataEvent dataEvent) {
  if (DataEvent.DATA_CREATED == dataEvent.getEventType()) {
   ....
  }
}

Migration from JSF 2.0 / 2.1

If you migrate a project from JSF 2.1 or earlier take care about the following rules:

Rule-1: Change Maven Dependencies for “javaee-api” from version 6 to version 7

Rule-2: For ViewScoped bean take care about the new annotation class which changed from javax.faces.bean.ViewScoped to javax.faces.view.ViewScoped

Rule-3: Update the namespaces in your faces-config.xml file

<faces-config 
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
...

Rule-4: Update the namespaces in your jsf pages

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:f="http://xmlns.jcp.org/jsf/core"
	xmlns:h="http://xmlns.jcp.org/jsf/html"
	xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
...

Use Plan HTML As Often As It Is Possible

If you develop an application over the years like myself, then a lot of code is created by copy & paste.  In JSF you will see than something like this:

<h:panelGroup layout="block" styleClass="toolbar">
   <h:panelGroup layout="block" styleClass="search">
     <h:panelGroup>
        .....

If you do not need a h:panelGroup for dynamic rendering and ajax behavior just replace it by pure HTML

<div styleClass="toolbar">
  <div styleClass="search">
    <span>
      .....

That makes your code cleaner and, which is more importantly, it improves the efficiency of your application. This is, because JSF builds a so called “component tree” during the life-cycle of a single request. This tree contains Java objects for each JSF core element and container. You don’t need these objects in your back-end if you just do some layout and styling as in the example above.  So this will just pollute your session.

Use always IDs For Your Containers

Connected with the previous rule, you should provide always your own IDs when using JSF containers.

<h:panelGroup id="toolbar">
  ...

If you don’t do this, JSF will give each container a internal unique id. This can result in something like this:

<div id=j_idt71:j_idt185:j_idt210>

This becomes a problem when you try to update content with AJAX.  If you need to update the outer component you have to know the exact ID to do so. But you can not know those IDs computed by the framework. If you use your own IDs you can do ajax like this:

<h:form id="my_form">
  ....
  <h:panelGroup id="toolbar">
    ....
      <h:commandLink actionListener="...." ...>
          <f:ajax render=":my_form:portlet_box" />
      </h:commandLink>
     ...
  </h:panelGroup>
  .....
  <h:panelGroup id="portlet_box">
    .....

This rendering from a container outside of your ajax request scope wont be possible if you do not use your own IDs.

That’s it

That’s it. I hope you can use these information and best practice for your own JSF application. If you have any suggestions just write a comment.

6 Replies to “JSF Best Practice”

  1. What is the objective behind loading the data in Request scoped controller and calling from View Scoped Controller. Wouldn’t it be easier just load the data from View scoped controller itself? Why two controllers?

  2. I use the request scoped controller just to avoid that the view result (which can be a lot of data) is stored in the user session on the server side. Of course you can hold the data in your ViewScoped Controller but it will consume more memory. But that doesn’t necessarily have to be a problem in your scenario.

    There is a new book form Bauke Schulz about JSF 10 which I can recommand here.
    https://www.amazon.de/Definitive-Guide-Jakarta-Faces-Applications/dp/1484273095/

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.