Message Handling with JSF and Redirects

Blog post from March 1st, 2009.

The JavaServer Faces-Framework offers a mechanism to present messages to the user. The meesages can be created by the framework itself, for example during the conversion oder validation phase. But it is also possible to create custom messages within your actual application.

But there is a problem, if you use redirects instead of forwards, e.g. by using the <redirect/> tag in faces.config.xml. A redirect leads to a second request loading the following page. The messages are only valid in the current request and are thus not displayed on the following page.

Two tags define, where the output of the messages should be placed:

<h:messages/> 
<h:message id="someid"/> 

See here for more information on the redirect problem: Forward versus redirect

I found a practical solution here:
MessageHandler Class

Just add this PhaseListener to your faces-config.xml:

package yourpackage; 
 
import java.util.List; 
import java.util.ArrayList; 
import java.util.Map; 
import java.util.Iterator; 
import javax.faces.event.PhaseListener; 
import javax.faces.event.PhaseId; 
import javax.faces.event.PhaseEvent; 
import javax.faces.context.FacesContext; 
import javax.faces.application.FacesMessage; 
 
/** 
* Enables messages to be rendered on different pages from which they were set. 
* To produce this behaviour, this class acts as a PhaseListener. 
* 
* This is performed by moving the FacesMessage objects: 
* 
* After each phase where messages may be added, this moves the messages from 
* the page-scoped FacesContext to the session-scoped session map. 
* 
* Before messages are rendered, this moves the messages from the session-scoped 
* session map back to the page-scoped FacesContext. 
* 
* Only messages that are not associated with a particular component are ever 
* moved. These are the only messages that can be rendered on a page that is different 
* from where they originated. 
* 
* To enable this behaviour, add a lifecycle block to your 
* faces-config.xml file. That block should contain a single phase-listener 
* block containing the fully-qualified classname of this file. 
* 
* @author <a href="mailto:jesse@odel.on.ca">Jesse Wilson</a> 
*/ 
 
public class MessageHandler implements PhaseListener { 
/** 
* a name to save messages in the session under 
*/ 
private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT"; 
 
/** 
* Return the identifier of the request processing phase during which this 
* listener is interested in processing PhaseEvent events. 
*/ 
public PhaseId getPhaseId() { 
  return PhaseId.ANY_PHASE; 
} 
 
/** 
* Handle a notification that the processing for a particular phase of the 
* request processing lifecycle is about to begin. 
*/ 
public void beforePhase(PhaseEvent event) { 
 
  if(event.getPhaseId() == PhaseId.RENDER_RESPONSE) { 
    FacesContext facesContext = event.getFacesContext(); 
    restoreMessages(facesContext); 
  } 
} 
 
/** 
* Handle a notification that the processing for a particular phase has just 
* been completed. 
*/ 
public void afterPhase(PhaseEvent event) { 
 
  if(event.getPhaseId() == PhaseId.APPLY_REQUEST_VALUES || 
  event.getPhaseId() == PhaseId.PROCESS_VALIDATIONS || 
  event.getPhaseId() == PhaseId.INVOKE_APPLICATION) { 
    FacesContext facesContext = event.getFacesContext(); 
    saveMessages(facesContext); 
  } 
 
} 
 
/** 
* Remove the messages that are not associated with any particular component 
* from the faces context and store them to the user's session. 
* 
* @return the number of removed messages. 
*/ 
private int saveMessages(FacesContext facesContext) { 
  // remove messages from the context 
  List messages = new ArrayList(); 
  for(Iterator i = facesContext.getMessages(null); i.hasNext(); ) { 
    messages.add(i.next()); 
    i.remove();  
  } 
  // store them in the session 
  if(messages.size() == 0) { 
    return 0; 
  } 
  Map sessionMap = facesContext.getExternalContext().getSessionMap(); 
  // if there already are messages 
  List existingMessages = (List)sessionMap.get(sessionToken); 
  if(existingMessages != null) { 
    existingMessages.addAll(messages); 
  } 
  else { 
    sessionMap.put(sessionToken, messages); // if these are the first messages 
  } 
 
  return messages.size(); 
} 
 
/** 
* Remove the messages that are not associated with any particular component 
* from the user's session and add them to the faces context. 
* 
* @return the number of removed messages. 
*/ 
private int restoreMessages(FacesContext facesContext) { 
  // remove messages from the session 
  Map sessionMap = facesContext.getExternalContext().getSessionMap(); 
  List messages = (List)sessionMap.remove(sessionToken); 
  // store them in the context 
  if(messages == null) { 
    return 0; 
  } 
  int restoredCount = messages.size(); 
  for(Iterator i = messages.iterator(); i.hasNext(); ) { 
    facesContext.addMessage(null, (FacesMessage)i.next()); 
  } 
 
  return restoredCount; 
} 
 
} 

Comments:

November 17th, 2010 at 5:23 pm | rainer Says:
very useful! added this functionality in my phaselistener taking care of message styling…works as expected :-) !

November 18th, 2010 at 1:52 pm | Jens Says:
Very Nice! Works well. This should be standard behaviour for jsf in my opinion…

February 2nd, 2011 at 9:54 am | Ravi Says:
Its pretty good solution..Thanks lot

October 19th, 2011 at 1:50 am | Gravity Layouts Says:
Mentioned at: Preserving FacesMessage after redirect for presentation through <h:message> in JSF:
[…] would I solve it? I found this amazing post explaining how to do it using a PhaseListener but I believe this situation is too common to have to […]