Persist and pass FacesMessages over multiple page redirects
LincolnVery Simple
In a JSF Reference Implementation, passing global faces messages between pages doesn’t work. It’s not designed that way “out of the box.” Fortunately there is a way to do this, which will even support redirects between pages, forwards through a RequestDispatcher, and also through standard JSF navigation cases.
There is a 5 minute solution to this problem.
Messages should be displayed IF:
- …the RENDER_RESPONSE phase has been reached, and JSF completed all phases “naturally.” This means that messages should not be displayed if the HttpResponse has been completed BEFORE the RENDER_RESPONSE phase has been reached.
- …the RENDER_RESPONSE phase is reached, and the HttpResponse is already completed, then FacesMessages could not have been rendered; they need to be saved again for the next RENDER_RESPONSE phase.
I found an almost solution to this problem in a mailing list that I’ve long since forgotten, but I saved the original accreditation, fixed the bugs (messages would not originally save through a redirect,) and here you go.
It’s a MultiPageMessagesSupport PhaseListener:
Copy this file into your project classpath.
package com.yoursite.jsf; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; /** * Enables messages to be rendered on different pages from which they were set. * * 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 global messages, not associated with a particular component, are * moved. Component messages cannot be rendered on pages other than the one on * which they were added. * * To enable multi-page messages support, add a <code>lifecycle</code> block to your * faces-config.xml file. That block should contain a single * <code>phase-listener</code> block containing the fully-qualified classname * of this file. * * @author Jesse Wilson jesse[AT]odel.on.ca * @secondaryAuthor Lincoln Baxter III lincoln[AT]ocpsoft.com */ public class MultiPageMessagesSupport implements PhaseListener { private static final long serialVersionUID = 1250469273857785274L; private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT"; public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } /* * Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we * have arrived here and the response is already complete, then the page is * not going to show up: don't display messages yet. */ // TODO: Blog this (MultiPageMessagesSupport) public void beforePhase(final PhaseEvent event) { FacesContext facesContext = event.getFacesContext(); this.saveMessages(facesContext); if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) { if (!facesContext.getResponseComplete()) { this.restoreMessages(facesContext); } } } /* * Save messages into the session after every phase. */ public void afterPhase(final PhaseEvent event) { if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) { FacesContext facesContext = event.getFacesContext(); this.saveMessages(facesContext); } } @SuppressWarnings("unchecked") private int saveMessages(final FacesContext facesContext) { List<FacesMessage> messages = new ArrayList<FacesMessage>(); for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();) { messages.add(iter.next()); iter.remove(); } if (messages.size() == 0) { return 0; } Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap(); List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken); if (existingMessages != null) { existingMessages.addAll(messages); } else { sessionMap.put(sessionToken, messages); } return messages.size(); } @SuppressWarnings("unchecked") private int restoreMessages(final FacesContext facesContext) { Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap(); List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken); if (messages == null) { return 0; } int restoredCount = messages.size(); for (Object element : messages) { facesContext.addMessage(null, (FacesMessage) element); } return restoredCount; } }
Configuration:
This needs to be in your faces-config.xml.
<phase-listener>
com.yoursite.jsf.MultiPageMessagesSupport
</phase-listener>
That’s it. You’re done.
References:
- JSF-RI: Specification Document
![]() | About the author:Lincoln Baxter, III is a Senior Software Engineer at Red Hat, Inc., working on JBoss open-source projects. This blog represents his personal thoughts and perspectives, not necessarily those of his employer. He is a founder of OcpSoft, the author of PrettyFaces: bookmarking, SEO extensions for JSF, and an individual member of the JavaServerâ„¢ Faces Expert Group. When he is not swimming, running, or playing Ultimate Frisbee, Lincoln is focused on promoting open-source software and making web-applications more accessible for small businesses, individuals. His latest project is ScrumShark, an open-source, agile project management tool. |


This is just what I need! Thanks!
Thanks a lot! That saved my day!!!
Works great! One question: I get duplicated FacesMessages when returning a String value of null from a JSF action method, to indicate the previous page should be rerendered. Returning a specific String works fine. Is there a way to work around this, or is it generally bad practice to return null from such methods?
Hmmm… Can you check to see if the method is being called twice? If so… why? What phases? It may depend on your implementation of JSF… You’re probably using MyFaces?
Hi,
Very nice, helped a lot…
I tried to implement a CDI version of your idea, see my questions on the weld forum :
http://www.seamframework.org/Community/PersistAndPassFacesMessagesOverMultiplePageRedirects
Thank you.
for (Iterator iter = facesContext.getMessages(null); iter.hasNext();) {
messages.add(iter.next());
iter.remove();
}
iter.remove(); doesn’t work on MyFaces, it’s probably because MyFaces return copy of collection so you remove item from copy not from orginal collection.
First message is saved in INVOKE_APPLICATION, second in RENDER_RESPONSE and also there is orginal message in facesContext so I get 3 same messages on page after all.
You could add a check to see if the message already exists. Only add the message if it is not already present in the list.
Thanks man, this solves my problem! PrettyFaces rocks
My pleasure, thank you!
Thanks Lincoln, this looks great! – it should tide me over until the Seam 3 Faces module is ready.
This will work with JSF2 won’t it?
Sure will
I’m actually writing up the docs on the Seam 3 docs right now. Also — those silly exceptions should be resolved in Alpha 3, as well. Thanks again for your help!