Spring - Redirect after POST (even with validation errors)

JavaSpringGoogle App-EngineSpring MvcValidation

Java Problem Overview


I'm trying to figure out how to "preserve" the BindingResult so it can be used in a subsequent GET via the Spring <form:errors> tag. The reason I want to do this is because of Google App Engine's SSL limitations. I have a form which is displayed via HTTP and the post is to an HTTPS URL. If I only forward rather than redirect then the user would see the https://whatever.appspot.com/my/form URL. I'm trying to avoid this. Any ideas how to approach this?

Below is what I'd like to do, but I only see validation errors when I use return "create".

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public final String submit(
    @ModelAttribute("register") @Valid final Register register,
    final BindingResult binding) {

    if (binding.hasErrors()) {
        return "redirect:/register/create";
    }

    return "redirect:/register/success";
}

Java Solutions


Solution 1 - Java

Since Spring 3.1 you can use RedirectAttributes. Add the attributes that you want to have available before doing the redirect. Add both, the BindingResult and the object that you are using to validate, in this case Register.

For BindingResult you will use the name: "org.springframework.validation.BindingResult.[name of your ModelAttribute]".

For the object that you are using to validate you will use the name of ModelAttribute.

To use RedirectAttributes you have to add this in your config file. Among other things you are telling to Spring to use some newer classes:

<mvc:annotation-driven />

Now the errors will be displayed wherever you are redirecting

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public final String submit(@ModelAttribute("register") @Valid final Register register, final BindingResult binding, RedirectAttributes attr, HttpSession session) {

if (binding.hasErrors()) {
    attr.addFlashAttribute("org.springframework.validation.BindingResult.register", binding);
    attr.addFlashAttribute("register", register);
    return "redirect:/register/create";
}

return "redirect:/register/success";
}

Solution 2 - Java

In addition to Oscar's nice answer, if you are following that RedirectAttributes approach, do not forget that you are actually passing the modelAttribute to the redirected page. This means if you create a new instance of that modelAttribute for the redirected page (in a controller), you will lose the validation errors. So, if your POST controller method is something like this:

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public final String submit(@ModelAttribute("register") @Valid final Register register, final BindingResult binding, RedirectAttributes attr, HttpSession session) {

if (binding.hasErrors()) {
    attr.addFlashAttribute("org.springframework.validation.BindingResult.register", binding);
    attr.addFlashAttribute("register", register);
    return "redirect:/register/create";
}

return "redirect:/register/success";
}

Then you will probably need to do a modification in your register create page GET controller. From this:

@RequestMapping(value = "/register/create", method = RequestMethod.GET)
public String registerCreatePage(Model model) {
    // some stuff
    model.addAttribute("register", new Register());
    // some more stuff
}

to

@RequestMapping(value = "/register/create", method = RequestMethod.GET)
public String registerCreatePage(Model model) {
    // some stuff
    if (!model.containsAttribute("register")) {
        model.addAttribute("register", new Register());
    }
    // some more stuff
}

Source: http://gerrydevstory.com/2013/07/11/preserving-validation-error-messages-on-spring-mvc-form-post-redirect-get/

Solution 3 - Java

I would question why you need the redirect. Why not just submit to the same URL and have it respond differently to a POST? Nevertheless, if you really want to do this:

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public final String submit(
    @ModelAttribute("register") @Valid final Register register,
    final BindingResult binding,
    HttpSession session) {

    if (binding.hasErrors()) {
        session.setAttribute("register",register);
        session.setAttribute("binding",binding);
        return "redirect:/register/create";
    }

    return "redirect:/register/success";
}

Then in your "create" method:

model.put("register",session.getAttribute("register"));
model.put("org.springframework.validation.BindingResult.register",session.getAttribute("register"));

Solution 4 - Java

The problem is you're redirecting to a new controller, rather than rendering the view and returning the processed form page. You need to do something along the lines of:

String FORM_VIEW = wherever_your_form_page_resides

...

if (binding.hasErrors())
    return FORM_VIEW;
   

I would keep the paths outside of any methods due to code duplication of strings.

Solution 5 - Java

The only way to persist objects between requests (ie a redirect) is to store the object in a session attribute. So you would include "HttpServletRequest request" in method parameters for both methods (ie, get and post) and retrieve the object via request.getAttribute("binding"). That said, and having not tried it myself you may need to figure out how to re-bind the binding to the object in the new request.

Another "un-nicer" way is to just change the browser URL using javascript

Solution 6 - Java

I don't know the exact issue with Google App Engine but using the ForwardedHeaderFilter may help to preserve the original scheme that the client used. This filter was added in Spring Framework 4.3 but some Servlet containers provide similar filters and the filter is self-sufficient so you can also just grab the source if needed.

Solution 7 - Java

Perhaps this is a bit simplistic, but have you tried adding it to your Model? I.e., include the Model in your method's arguments, then add the BindingResult to it, which is then available in your view.

model.addAttribute("binding",binding);

I think you may have to use a forward rather than a redirect (in my head I can't remember if a redirect loses the session or not — I could be wrong about this as I don't have any documentation handy, i.e., if you're not getting the BindingResult after adding it to the Model, try using a forward instead to confirm this).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTaylor LeeseView Question on Stackoverflow
Solution 1 - JavaOscarView Answer on Stackoverflow
Solution 2 - JavaUtku ÖzdemirView Answer on Stackoverflow
Solution 3 - JavarjsangView Answer on Stackoverflow
Solution 4 - JavaLewisView Answer on Stackoverflow
Solution 5 - JavaklonqView Answer on Stackoverflow
Solution 6 - JavaRossen StoyanchevView Answer on Stackoverflow
Solution 7 - JavaIchiro FurusatoView Answer on Stackoverflow