Java EE CDI ConversationScoped example

13 April 2013
By Gonçalo Marques
In this tutorial you will learn how to use ConversationScoped CDI beans.

Introduction

As we have seen previously on Java EE CDI bean scopes CDI provides a set of available built-in bean scopes. One of these scopes is the Conversation scope. In this tutorial we will see how this scope can be used to establish a conversation between client and server in order to achieve two distinct goals:

  1. Alow AJAX interaction between client and server

  2. Complete a set of related tasks, each one in a separate web page (ex: a registration form with multiple steps)

This tutorial considers the following software and environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.09
  3. Weld 1.1.10
  4. Tomcat 7.0.35

Following next are the required Maven dependencies in order to run CDI on Tomcat:

Required Maven dependencies

<dependencies>
  <dependency>
    <groupId>org.jboss.weld.servlet</groupId>
    <artifactId>weld-servlet</artifactId>
    <version>1.1.10.Final</version>
  </dependency>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

We will be using Weld - The reference CDI implementation

Fundamentals

When we annotate a CDI managed bean with @ConversationScoped we are defining the bean as being able to track a conversation with a client. The bean will maintain its state until the conversation ends.

Meanwhile - during the conversation - the client may execute AJAX requests against the bean or navigate to other pages that still reference this same managed bean. In these cases the bean will keep its state across client interactions.

How does the container accomplished this task? When a conversation is established the container will generate a conversation identifier. This identifier will be appended to every URL the client uses to communicate with the server since the conversation was established. This way the container knows exactly which conversation scoped bean instance to fetch in order to serve the client request.

When a conversation is started it will be in transient state so it will be terminated as soon as the request that originated the conversation also terminates. If we want the conversation to change into long-running mode - the state we are looking for - we have to explicitly instruct this (we will see it in the next sections).

In this tutorial we will use JSF in order to implement the front-end. JSF will assure that any request generated by a given client will append the conversation identifier to the request URL as long as a conversation as been established.

The example

We will implement a conversation that consists in three distinct steps:

  1. The first step will consist in a simple counter that gets incremented by the client in an AJAX style

  2. The second step will be the outcome of a POST request generated by the first step, where JSF will automatically append the conversation identifier to the REDIRECT url. The counter state will be preserved.

  3. The third and final step will be the outcome of a GET request generated by a click in a hyperlink from the second step. JSF will generate the URL containing the conversation identifier also automatically.

The managed bean

Following next is the managed bean used in the example in order to implement the three steps described in the previous section:

ConversationBean.java

package com.byteslounge.bean;

import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@ConversationScoped
public class ConversationBean implements Serializable {

  private static final long serialVersionUID = 4771270804699990999L;
  
  @Inject
  private Conversation conversation;
  
  private int counter;
  
  // Will only be called once
  // during bean initialization
  @PostConstruct
  public void init(){
    counter = 0;
  }
  
  public void initConversation(){
    if (!FacesContext.getCurrentInstance().isPostback() 
      && conversation.isTransient()) {
      
      conversation.begin();
    }
  }
  
  public void increment(){
    counter++;
  }
  
  public String handleFirstStepSubmit(){
    return "step2?faces-redirect=true";
  }
  
  public String endConversation(){
    if(!conversation.isTransient()){
      conversation.end();
    }
    return "step1?faces-redirect=true";
  }

  public int getCounter() {
    return counter;
  }

  public void setCounter(int counter) {
    this.counter = counter;
  }

  public Conversation getConversation() {
    return conversation;
  }
  
}

Things to note in the managed bean:

We are injecting a conversation instance via @Inject annotation.

The method initConversation() will be called when the first page is generated by the means of the JSF preRenderView event (we will see this in the next section). This method will start the conversation only if this is not a postback or if the conversation is not already started.

Step One

Following is the xhtml of the first step:

step1.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core">
<f:event listener="#{conversationBean.initConversation}"
  type="preRenderView"></f:event>
<h:head>
  <title>First step</title>
</h:head>
<h:body>
  <h:outputText value="First step"></h:outputText>
  <br />
  <br />
  <h:form>
    <h:outputText value="#{conversationBean.counter}"></h:outputText>
    <br />
    <h:commandButton value="Increment" type="submit">
      <f:ajax execute="@form" listener="#{conversationBean.increment}"
        render="@form" />
    </h:commandButton>
    <br />
    <br />
    <h:commandLink action="#{conversationBean.handleFirstStepSubmit}"
      value="Go to step 2" />
  </h:form>
</h:body>
</html>

This page consists in a counter that will be incremented every time we click the Increment button: We may easily implement AJAX functionality with conversation scoped beans. When we click the Go to step 2 command link we will call handleFirstStepSubmit() bean method by the means of HTTP POST:

method handleFirstStepSubmit()

public String handleFirstStepSubmit(){
  return "step2?faces-redirect=true";
}

This method will result in a REDIRECT being sent to the client by standard JSF redirection. The conversation identifier will be automatically appended to the generated step 2 URL so the container will be able to fetch the same conversation bean instance in step 2.

Also note that we are instructing preRenderView event to call initConversation() bean method so the conversation may be established in the first place.

Step Two

Following is the xhtml of the second step:

step2.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core">
<h:head>
  <title>Second step</title>
</h:head>
<h:body>
  <h:outputText value="Second step"></h:outputText>
  <br />
  <br />
  <h:outputText value="#{conversationBean.counter}"></h:outputText>
  <br />
  <br />
  <h:link outcome="/step3.xhtml" value="Go to step 3">
    <f:param name="cid" value="#{conversationBean.conversation.id}" />
  </h:link>
</h:body>
</html>

Since the URL that was generated on the first step included the conversation identifier, when the second step is initialized and the conversationBean is referenced, the container will know how to fetch the correct conversation bean instance.

The counter will present the exact same state - or value - that it was left on the first step: The conversation bean instance was fetched and the state was correctly preserved.

We are also generating a link to the third and final step. Note that this is not the same as the transition from the step 1 to step 2: This time we are generating a link that will perform a GET request to the final step. The conversation identifier will be automatically appended to the generated URL.

Step Three

Following is the xhtml of the third step:

step3.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core">
<h:head>
  <title>Third step</title>
</h:head>
<h:body>
  <h:outputText value="Third step"></h:outputText>
  <br />
  <br />
  <h:outputText value="#{conversationBean.counter}"></h:outputText>
  <br />
  <br />
  <h:form>
    <h:commandLink action="#{conversationBean.endConversation}"
      value="End conversation" />
  </h:form>
</h:body>
</html>

In the third step we will also present the counter: The correct bean instance was once again correctly fetched by the container since the conversation identifier was appended to the third step request URL. This time we will use a commandLink to call endConversation() bean method:

method endConversation()

public String endConversation(){
  if(!conversation.isTransient()){
    conversation.end();
  }
  return "step1?faces-redirect=true";
}

This method will end the conversation and redirect the client once again to the first step. Since the conversation was explicitly ended a new conversation will be started in step 1.

The example in action

When we deploy the application in tomcat and access step1 URL:

CDI conversation - first step

We increment the counter by clicking the Increment button. AJAX requests are sent to the server in order to increment the counter in the bean (server-side). When we click the command link Go to step 2:

CDI conversation - second step

A POST was sent to the server which in turn responded with a redirect to the step 2 page. As we can see the conversation identifier was appended to the request URL so the container was able to fetch the correct bean instance: The counter state is the same as we left it in step 1.

When we click the Go to step 3 link a GET request will be generated and step 3 page will be fetched. The conversation identifier will be also appended to the URL so the correct bean instance will also be used by the container:

CDI conversation - third step

Finally we now click End conversation command link so the bean method endConversation() will be called. As we have seen before this will end the conversation and redirect the user to the first step.

The conversation identifier will no longer be appended to the generated URL and a new conversation bean will be initialized by the container, meaning that the state will also be the initial one: counter value is zero:

CDI conversation restarted

Conclusion

As you can see it's easy to use ConversationScoped beans to implement a conversation between client and server. This conversation may be used to implement AJAX interaction between the client and the server and also to maintain state across distinct page requests. The last one may be useful to complete related tasks, like a registration form containing multiple steps (first step to gather the user personal information, second step to gather the user interests, and so on).


Note: Keep in mind that if the user leaves your conversation without giving you the chance of ending that same conversation, the managed bean will stay active until it times out. So if you have a real problem related with these beans remaining active until timeout, and really need the beans to behave as JSF View scoped beans, you should look into the following alternatives:
  • Apache DeltaSpike (former Seam-Faces)

  • ViewAccessScope beans from MyFaces CODI

The tutorial source code is available for download at the end of this page.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: