I recently ran into a rather common issue with using SWF: messages are not preserved across a top-level flow redirect. I didn't find any good solutions here (or was I just not looking hard enough ), so I came up with something which solves the problem, and thought I'd share it with anyone interested.

MessagePreserver is a FlowExecutionListener overriding the sessionEnding() and sessionStarting() methods. It's looking for an attribute in the end-state called PRESERVE_MESSAGES. If it finds it, it saves the message snapshot to session scope under PRESERVED_FLOW_MESSAGES. In sessionStarting(), if it finds the snapshot in session scope, it restores the messages.

To use this, you'll need to add the bean to your webflow config file and add it as a flow execution listener. Whenever you need to preserve across a redirect, add the PRESERVE_MESSAGES attribute to the end-state. Here's the code:

Code:
// Use your own package!

import java.io.Serializable;

import org.springframework.binding.message.DefaultMessageContext;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;

public class MessagePreserver extends FlowExecutionListenerAdapter  {
  public static final String PRESERVE_MESSAGES = "PRESERVE_MESSAGES";
  private static final String PRESERVED_FLOW_MESSAGES = "PRESERVED_FLOW_MESSAGES";
  

  public void sessionStarting(RequestContext requestContext, 
      FlowSession flowSession, MutableAttributeMap attributeMap) {
    Serializable preservedMessages = (Serializable) 
        requestContext.getExternalContext().getSessionMap().get(PRESERVED_FLOW_MESSAGES);
        
    if (preservedMessages != null) {
      DefaultMessageContext currentMessageContext = 
          (DefaultMessageContext) requestContext.getMessageContext();
      currentMessageContext.restoreMessages(preservedMessages);
      requestContext.getExternalContext().getSessionMap().remove(PRESERVED_FLOW_MESSAGES);
    }
  }

  public void sessionEnding(RequestContext requestContext, 
      FlowSession flowSession, String string, MutableAttributeMap attributeMap) {
    
    if (requestContext.getCurrentState().getAttributes().contains(PRESERVE_MESSAGES)) {
      Serializable messageSnapshot = 
          ((DefaultMessageContext) requestContext.getMessageContext()).createMessagesMemento();
    
      requestContext.getExternalContext().getSessionMap().
          put(PRESERVED_FLOW_MESSAGES, messageSnapshot);
    }
  }
}