Preserving Messages Across a Redirect in Struts 2

In Struts 2 when your result type is a redirect (or redirectAction) you will loose any error messages that your action created unless you take special steps to preserve them.

The standard response to the question “How do you preserve messages across a redirect?” is to point the person to the MessageStoreInterceptor“.  I took a look at using this in my own projects but frankly it was a major pain to configure it.  You need to manually configure the interceptor to store the messages in every action that might have a redirect result and you have to also configure it to retrieve the message for every action that is the target of the redirect…. yuck.  Too much work.

So I wrote the RedirectMessageInterceptor (see below) to simplify the solution.   It makes the assumption that you always want to preserve messages across a redirect and restore them to the next action if they exist.   The way it works is… it looks at the result type after a action has executed and if the result was a redirect (ServletRedirectResult) or a redirectAction (ServletActionRedirectResult) and there were any errors, messages, or fieldErrors they are stored in the session.  Before the next action executes it will check if there are any messages stored in the session and add them to the next action.

The one thing you need to be aware of is: The action you are redirecting towards will need to configure a result with name=”input” as the added messages will trigger the ‘workflow’ interceptor to return a result of “input”.

To use this you simply need to declare it and add it to your stack.  It extends MethodFilterInterceptor so you can configure ‘excludeMethods’ and ‘includeMethods’ (I haven’t been able to think of a good reason why you would want to do this but it comes for free so you can if you like.)

<interceptor name="redirectMessage"
      class="my.struts.interceptor.RedirectMessageInterceptor" />
<interceptor-stack name="myStack">
    <interceptor-ref name="redirectMessage" />
    <interceptor-ref name="paramsPrepareParamsStack" />
</interceptor-stack>
<default-interceptor-ref name="myStack" />

Here is the Interceptor feel free to use it or modify it as you see fit.

This has been updated to work with Struts 2.1.6. If you are using an earlier version of Struts you may need to add some @SuppressWarnings(“unchecked”)  annotations. 3/13/09

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package my.struts.interceptor;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.ServletActionRedirectResult;
import org.apache.struts2.dispatcher.ServletRedirectResult;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

/**
 * An Interceptor to preserve an actions ValidationAware messages across a
 * redirect result.
 *
 * It makes the assumption that you always want to preserve messages across a
 * redirect and restore them to the next action if they exist.
 *
 * The way this works is it looks at the result type after a action has executed
 * and if the result was a redirect (ServletRedirectResult) or a redirectAction
 * (ServletActionRedirectResult) and there were any errors, messages, or
 * fieldErrors they are stored in the session. Before the next action executes
 * it will check if there are any messages stored in the session and add them to
 * the next action.
 *
 */
public class RedirectMessageInterceptor extends MethodFilterInterceptor
{
    private static final long  serialVersionUID    = -1847557437429753540L;

    public static final String FIELD_ERRORS_KEY    = "RedirectMessageInterceptor_FieldErrors";
    public static final String ACTION_ERRORS_KEY   = "RedirectMessageInterceptor_ActionErrors";
    public static final String ACTION_MESSAGES_KEY = "RedirectMessageInterceptor_ActionMessages";

    public RedirectMessageInterceptor()
    {
    }

    public String doIntercept(ActionInvocation invocation) throws Exception
    {
        Object action = invocation.getAction();
        if (action instanceof ValidationAware)
        {
            before(invocation, (ValidationAware) action);
        }

        String result = invocation.invoke();

        if (action instanceof ValidationAware)
        {
            after(invocation, (ValidationAware) action);
        }
        return result;
    }

    /**
     * Retrieve the errors and messages from the session and add them to the
     * action.
     */
    protected void before(ActionInvocation invocation,
                          ValidationAware validationAware)
        throws Exception
    {
        @SuppressWarnings("unchecked")
        Map<String, ?> session = invocation.getInvocationContext().getSession();

        @SuppressWarnings("unchecked")
        Collection<String> actionErrors =
            (Collection) session.remove(ACTION_ERRORS_KEY);
        if (actionErrors != null && actionErrors.size() > 0)
        {
            for (String error : actionErrors)
            {
                validationAware.addActionError(error);
            }
        }

        @SuppressWarnings("unchecked")
        Collection<String> actionMessages =
            (Collection) session.remove(ACTION_MESSAGES_KEY);
        if (actionMessages != null && actionMessages.size() > 0)
        {
            for (String message : actionMessages)
            {
                validationAware.addActionMessage(message);
            }
        }

        @SuppressWarnings("unchecked")
        Map<String, List<String>> fieldErrors =
            (Map) session.remove(FIELD_ERRORS_KEY);
        if (fieldErrors != null && fieldErrors.size() > 0)
        {
            for (Map.Entry<String, List<String>> fieldError : fieldErrors.entrySet())
            {
                for (String message : fieldError.getValue())
                {
                    validationAware.addFieldError(fieldError.getKey(), message);
                }
            }
        }
    }

    /**
     * If the result is a redirect then store error and messages in the session.
     */
    protected void after(ActionInvocation invocation,
                         ValidationAware validationAware)
        throws Exception
    {
        Result result = invocation.getResult();

        if (result != null 
            && (result instanceof ServletRedirectResult ||
                result instanceof ServletActionRedirectResult))
        {
            Map<String, Object> session = invocation.getInvocationContext().getSession();

            Collection<String> actionErrors = validationAware.getActionErrors();
            if (actionErrors != null && actionErrors.size() > 0)
            {
                session.put(ACTION_ERRORS_KEY, actionErrors);
            }

            Collection<String> actionMessages = validationAware.getActionMessages();
            if (actionMessages != null && actionMessages.size() > 0)
            {
                session.put(ACTION_MESSAGES_KEY, actionMessages);
            }

            Map<String, List<String>> fieldErrors = validationAware.getFieldErrors();
            if (fieldErrors != null && fieldErrors.size() > 0)
            {
                session.put(FIELD_ERRORS_KEY, fieldErrors);
            }
        }
    }
}

38 Responses to Preserving Messages Across a Redirect in Struts 2

  1. […] ActionMessages and ActionErrors between the first and second action. But Glindholm solved the problem with a simple […]

  2. tkrah says:

    Some enhancement request:

    Add

    || result instanceof PortletActionRedirectResult

    to the resultType Check to get this interceptor working in a PortletEnvironment.

  3. Sajid says:

    I tried

    unable to preserve erroe messages 😦 please help

  4. Nathan says:

    Thanks for the class! It solved my problem perfectly! You rock!

  5. Siddiq says:

    Thanks a lot.
    I work absoultly fine. You rock 🙂

  6. Dean says:

    Quick question:
    When you say I should configure an input parameter in the struts.xml, could you provide an example?

    I’m stuck and keep getting the same error that says I need an input

    • glindholm says:

      The action you are redirecting towards will need to configure a result with name=”input” as the added messages will trigger the ‘workflow’ interceptor to return a result of “input”.

      See result-configuration

      • Dean says:

        I just want to go from Login.action to Process.action. If Process.action gets errors, I want to return to Login.action (execute()) and have it display the appropriate errors.

        what should I have for the result input statement if I want to go to the Login.action class?

        struts.xml snippet

        ${loginTemplate}

        Process
        /jsp/ErrorPage.jsp

        Login
        /jsp/ErrorPage.jsp

      • glindholm says:

        I understand what you want but that is not how the Struts validation framework works. If there are any validation errors then the WorkFlow interceptor will stop the request processing and transfer control to the ‘input’ result and the execute() method for the action will never be called.

        If you need further help please send a request to the Struts Mail List http://struts.apache.org/mail.html you can reference this interceptor.

  7. I’m trying to do a redirect with annotations: @Result(name = “success”, type=”redirect”, location=”home.action”), however, the resulting ActionInvocation is ServletDispatcherResult instead of ServletRedirectResult so it is not handled by the after() method. I could modify after() to process ServletDispatcherResult too, but I’d rather not. Do you see why my @Result doesn’t return ServletRedirectResult?

    • glindholm says:

      Sorry, I have no idea why you are not getting a Redirect result but it doesn’t seem to have anything to do with this filter. Maybe you should post your question on the Struts User mailing list.

  8. Jay says:

    Dude, The MessageStoreInterceptor has an AUTOMATIC operationMode that will, yes, automatically store messages/errors across redirects. Redirects between other Actions only, mind you. Not redirects to JSPs! Why redirect to JSPs?? My answer, the same (and more) reasons why you redirect to anything else. For funzies try throwing the ExecAndWait Interceptor onto the stack (bottom) to see a lot of things blow up. :-). But, thanks for contiributing to keeping the Struts2 community alive.

  9. Roelof_ says:

    Works like a charm. Thanks.

  10. Card Tricks says:

    Thanks! worked perfectly. before i saw this solution I tried to override the the methods getActionMessages and addActionMessage to save the messages through the session, but it gave me freemarker problems for some reason.

  11. Kevin Hubilla says:

    Your Interceptor works. But how about redirecting to an Action with “method”? I found a problem when using your interceptor with that scenario.

    • glindholm says:

      It’s best if you post your problem (with complete info) to the Struts User Mailing list.

      • Kevin Hubilla says:

        Even if the problem is because of your interceptor?

      • glindholm says:

        Yes please post a complete description of your problem with source code, and configuration information to the Struts Users mailing list. Your other option (the better one) is to run your application in the debugger and step thru this interceptor (you have the source) and then identify which line or statement is incorrect, then let me know. If you do this and discover it’s has nothing to do with this interceptor then you can post your question to the Struts Users mailing list. BTW it is generally considered rude to blame someone else’s code for your problems without providing some evidence to support your claim.

      • Kevin Hubilla says:

        Sorry for the the rude attitude but im not trying to blame your code. maybe i just can’t express it well. 🙂 sorry sir. I’ve found out that the “paramsPrepareParamsStack” interceptor does the problem, i’m not sure though, but if i remove it, the redirect message works but removes the ability of XML validation. anyways, thank you

  12. Mr. Magoo says:

    Did anyone ever try just passing the action errors as a parameter? I am using an old version of struts2, but this works perfectly fine, and is much simpler.

    actionName
    ${actionErrors}

    I am using the most recent version of struts2 at home… I might give this a try tonight and post the results.

    • glindholm says:

      (I think your post may have been slightly mangled by wordpress to remove the tags.)

      You have 3 collections of messages to deal with, actionErrors, actionMessages, and fieldErrors, and you need to get these message collections populated in the next action in order for the message rendering tags to work as expected.

      Your suggestion (if it even works) would require you to take extra steps when ever you did a redirect as a result.

      The point of this interceptor is to allow the standard error message handling to “just work” when you do a redirect the same as it works when your result is a forward. No extra configuration needed, no special case for redirect.

  13. Praveen says:

    Hi, It is working great for me, however in some situation when the action has been redirected, message did not appear on that forwarded jsp. However when user click some on that page , then it shows on the other action which is not relevant to that action message. is there any thing that is wrong am i doing. It only happening intermittently. Any thoughts would be great. I added this interceptor as my top in my default stack.

    • glindholm says:

      If you redirect to an action that has a different interceptor stack (without this one) then it would not show up. Likewise if you redirect to something other then an action, e.g. directly to a jsp or and html page etc. anything that isn’t an action won’t be processed by the interceptor. If this happens the messages will still be sitting in the session and will be shown at the next action you process with this interceptor. Always redirect to an action never directly to a jsp.

      • Praveen says:

        Thanks for your reply. I am always redirecting to action and it is working great except that some times (like 1 in 100 iterations) it fails to show the message in the redirected action. I tried lot of debugs etc., to figure out. No luck so far. And this happening only for a particular action.

      • encho says:

        I have experienced the same problem as Praveen, and I believe that the cause is that the browser is receiving and acting upon the redirect BEFORE the after() method in the interceptor is called. This is only likely to happen when running both browser and sever on the same computer. A possible solution would be to instruct Struts2 not to flush the redirect command until all interceptors have completed. Do you know whether this is possible?

      • encho says:

        Further to my previous comment, I determined that this problem was ultimately caused by an Ajax request being given the messages instead of the “main” request. Amending the interceptor to ignore Ajax requests in the before() method appears to have solved it. Many thanks for a great article.

  14. EMMERICH says:

    Worked well for me. It’s worth noting that, in Struts 2.1.8 at least, that you should separate the interceptor declaration and the default-interceptor reference in your struts.xml.

    It should look like this:

    <interceptors>
    <interceptor name="redirectMessage" class="my.struts.interceptor.RedirectMessageInterceptor" />
    <interceptor-stack name="myStack">
        <interceptor-ref name="redirectMessage" />
        <interceptor-ref name="paramsPrepareParamsStack" />
    </interceptor-stack>
    </interceptors>
    
    <default-interceptor-ref name="myStack" />
    
    // your actions here
    

    The way you wrote it, obviously, isn’t wrong. Just thought I’d help improve the clarity.

  15. […] Things get messy when your “input” result redirects to another action. Firstly, you’ll lose the error messages. WordPress user Glindholm outlined an excellent solution to this problem here. […]

  16. Saul says:

    Why isn’t this interceptor in the default stack?! How many developers used chain instead of redirect just because they didn’t have it in hand? It’s obviously the standard desired behavior. You rock! Thanks for sharing…

  17. […] faz um redirect as mensagens de erros são perdidas. Mas implementando o interceptor que está aqui as mensagens são propagadas. Share:FacebookTwitterEmailLike this:LikeBe the first to like this […]

  18. Steven says:

    Thanks a ton.It is informative.

  19. Karan Kapoor says:

    This works like a charm. Thanks for helping out.

  20. John says:

    Works like a charm in Struts2 2.3.14 !! Thanks ! Exactly what i needed.

  21. chaitanya desu says:

    Worked like magic

  22. Aditya Patil says:

    Thanks ! This is what I needed !

  23. Charles says:

    I tried using your custom interceptor as you instructed. When I click on the button that will submit to the Action that will result in a redirectAction, I get the following error:

    No result defined for action actions.WPSCommunicationDelete_Confirmation and result input

    I have result type input defined for all of my actions. The error message doesn’t make sense.

    Do you have any idea what might be causing this error message?

    Thanks,

    Charles

    • glindholm says:

      The error message is coming from Struts and is telling you it can’t find a result of ‘input’ for that action. All I can suggest is to check the action actions.WPSCommunicationDelete_Confirmation and ensure it has a result of input defined.

Leave a reply to Dean Cancel reply