Performing a redirect from a spring MVC @ExceptionHandler method
Asked Answered
E

4

16

I want to have the following method:

@ExceptionHandler(MyRuntimeException.class)
public String myRuntimeException(MyRuntimeException e, RedirectAttributes redirectAttrs){//does not work
    redirectAttrs.addFlashAttribute("error", e);
    return "redirect:someView";
}

I get a:

java.lang.IllegalStateException: No suitable resolver for argument [1] type=org.springframework.web.servlet.mvc.support.RedirectAttributes]

Is there a way to perform a redirect from an @ExceptionHandler? Or maybe some way to circumvent this restriction?

EDIT:

I have modified my exception handler as follows:

@ExceptionHandler(InvalidTokenException.class)
public ModelAndView invalidTokenException(InvalidTokenException e, HttpServletRequest request) {
RedirectView redirectView = new RedirectView("signin");
return new ModelAndView(redirectView , "message", "invalid token/member not found");//TODO:i18n
}

This is the method that may throw the exception:

@RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(@PathVariable("token") String token) {
    signupService.activateMember(token);
    return "redirect:memberArea/index";
}

The problem with my modified exception handler is that it systematically redirects me to the following URL:

http://localhost:8080/bignibou/activateMember/signin?message=invalid+token%2Fmember+not+found 

Instead of:

http://localhost:8080/bignibou/signin?message=invalid+token%2Fmember+not+found

EDIT 2:

Here is my modified handler method:

@ExceptionHandler(InvalidTokenException.class)
public String invalidTokenException(InvalidTokenException e, HttpSession session) {
session.setAttribute("message", "invalid token/member not found");// TODO:i18n
return "redirect:../signin";
}

The problem I now have is that the message is stuck in the session...

Eurythmics answered 19/2, 2013 at 16:3 Comment(1)
Hi did you ever find a solution for doing it without adding query string paramenters?Barbicel
B
28

Note that this is actually supported out-of-the-box by Spring 4.3.5+ (see SPR-14651 for more details).

I've managed to get it working using the RequestContextUtils class. My code looks like this

@ExceptionHandler(MyException.class)
public RedirectView handleMyException(MyException ex,
                             HttpServletRequest request,
                             HttpServletResponse response) throws IOException {
    String redirect = getRedirectUrl(currentHomepageId);

    RedirectView rw = new RedirectView(redirect);
    rw.setStatusCode(HttpStatus.MOVED_PERMANENTLY); // you might not need this
    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
    if (outputFlashMap != null){
        outputFlashMap.put("myAttribute", true);
    }
    return rw;
}

Then in the jsp page I simply access the attribute

<c:if test="${myAttribute}">
    <script type="text/javascript">
      // other stuff here
    </script>
</c:if>

Hope it helps!

Barbicel answered 10/4, 2013 at 17:18 Comment(7)
If you don't care about the visibility of such an attribute you can simply pass it as a query parameter to RedirectView, it gets then added to the flash map internally. For example return new RedirectView("/home?param=1", true);.Carolinecarolingian
yes, that is the easy case. I had a specific requirement to avoid parameters for SEO reasons. Apparently search engines would treat the URLs as two separate pagesBarbicel
mericano: I am very sorry I took some much time to look at your answer. It just works perfectly! Thanks, Accepted!!Eurythmics
Excellent, I did not know about RequestContextUtils. Thanks!Standice
Where that getRedirectUrl comes form?Mamelon
@TeodorKolev It's just a method that figures out a location to redirect to for the currentPage.Barbicel
@Barbicel It seems getOutputFlashMap() doesn't return null and you can eliminate the check for null.Lyndonlyndsay
S
7

You could always forward then redirect (or redirect twice).. First to another request mapping where you have normal access to RedirectAttributes, then again to your final destination.

    @ExceptionHandler(Exception.class)
    public String handleException(final Exception e) {

        return "forward:/n/error";
    }

    @RequestMapping(value = "/n/error", method = RequestMethod.GET)
    public String error(final RedirectAttributes redirectAttributes) {

        redirectAttributes.addAttribute("foo", "baz");
        return "redirect:/final-destination";
    }
Sheldon answered 12/9, 2013 at 22:13 Comment(2)
This is a very elegant solution, but during this forward the exception infomation gets lost.Brottman
What information is lost? The exception information? Using an @ExceptionHandler typed to Exception.class should be a last resort. You should be handling known exception cases by more specific Exception classes. In that case, you could forward to more specific methods that redirects after tacking on whatever exception-specific info that your final destination needs. In this simple example we just want to know what something went wrong, and yes, no details about the exception make it to the redirecting method.Sheldon
A
3

I am looking at the JavaDoc and I don't see where RedirectAttributes is a valid type that is accepted.

Antifreeze answered 19/2, 2013 at 18:26 Comment(14)
That's precisely my point. RedirectAttributes is not accepted (see my post above) and I am looking for a way to circumvent this problem...Eurythmics
I dont see a work-around that provides you with the ability to use Flash Attributes. Your only options are to return a ModelAndView and set the view to a RedirectView and set the attributes as needed. I believe this will cause the attributes to be passed on the qeuery, though, which is probably not what you want.Antifreeze
I have modified my post above. I have a problem with the redirection. I don't understand why I am not redirected to: /signin?message=invalid+token%2Fmember+not+foundEurythmics
Try putting a '/' in front of the signin. So, your RedirectView should look like: RedirectView redirectView = new RedirectView("/signin");Antifreeze
The leading "/signin" resolves to "localhost:8080/signin" (notice the app context is gone). It is very strange: I had to use: "../signin" in order for it to work.Eurythmics
And as you said earlier on, the attributes are passed on the query which is not ideal...Eurythmics
Yeah, the problem is that a redirect comes as a HTTP 302, which I am almost certain cannot contain POST data. The only way to send data via a redirect is by passing them on the URL. Since it looks like you are trying to handle an error and "redirect" within the same server, one possible solution might be to put the error message in session. You would then simply check the session to see if you had an error message on the signin page, and your redirect URL would be much cleaner.Antifreeze
Two things strike me: 1. I can't use RedirectAttributes with an @ExceptionHandler method. 2. Most importantly, I can't use flash attributes "manually" without resorting to the RedirectAttributes class...Eurythmics
Directly from the Spring Docs: "Unlike other redirect attributes, which end up in the target redirect URL, flash attributes are saved in the HTTP session (and hence do not appear in the URL). The model of the controller serving the target redirect URL automatically receives these flash attributes after which they are removed from the session." So, all Flash Attributes do, according to that statement, is set the values in Session for you. So, essentially, you don't need RequestMappingHandlerAdapter.Antifreeze
Ummm. What do you mean by "you don't need RequestMappingHandlerAdapter"??Eurythmics
Sorry, meant you don't need a RedirectAttributes. I copied and pasted the wrong line. All the RedirectAttributes is doing is putting the values in session and removing them once the redirect occurs. There's some fancy mapping of the URL->Session Values going on to make sure the right request gets the right values prior to removing them from Session, but that's it.Antifreeze
I see. But is there not a way to set a flash attribute without the RedirectAttributes class or do you really think I need to manually set a session attribute and then clear the session as you advise?Eurythmics
Also: can you please advise where and how do I clear the session attribute if I "manually" use the session as indicated in your above comment?Eurythmics
The Java EE HttpSesssion object has a method called removeValue(). There is also a JSTL tag <c:remove> that you can use in the JSP to remove variables from session. As for setting flash variables, reading the Spring docs might help you there. If I understand them correctly the "magic" happens in the FlashMap, but how you get one and what you have to do with it I can't say. I don't think you are supposed to mettle with that, and I would personally consider any code that does a "hack".Antifreeze
K
1

Since Spring 4.3.5 (SPR-14651) you can use stright away your first aproach:

@ExceptionHandler(MyRuntimeException.class)
public String myRuntimeException(MyRuntimeException e, RedirectAttributes redirectAttrs){
    redirectAttrs.addFlashAttribute("error", e);
    return "redirect:someView";
}
Kosciusko answered 29/5, 2018 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.