Keep form field values and errors after redirection in Spring MVC controller

Ways for processing form submit

When you are processing a form submit using Spring MVC, if there are validation errors, you can:

  1. Return the errors in the response to the form submit request (POST request usually).
  2. Redirect the user to the form and return the result from this new request.

The first option has a drawback that if the user refreshes the page after we return with the response, the form will be resubmitted the same way as before (also he’ll see the confirmation popup asking if it is okay to do that). On one hand, most of the users won’t even understand the situation, on the other hand if the user decides to resubmit the form, sometimes unwanted situations might happen.

Also, another case to consider, if the user just wants to refresh the page to get an empty form, he’ll not succeed because he’ll still see the errors and filled values from the previous submission.

Because of these reasons the second option might be preferred, let’s see how to do that

Retaining filled values and errors upon redirect

First let’s see the whole code of a controller that implements the solution:

package com.devsphinx.web.controller.user;

import com.devsphinx.web.controller.BaseController;
import com.devsphinx.web.model.user.CreateAccountModel;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;

@Controller
public class CreateAccountController extends BaseController {

    @RequestMapping(value = "/create-account", method = RequestMethod.GET)
    public String getCreateAccount(Model model) {

        if (!model.containsAttribute("createAccountModel")) {
            model.addAttribute("createAccountModel", new CreateAccountModel());
        }

        return "create-account/create-account";
    }

    @RequestMapping(value = "/create-account", method = RequestMethod.POST)
    public String postCreateAccount(
            @Valid CreateAccountModel createAccountModel,
            BindingResult result, RedirectAttributes redirectAttributes) {

        if (result.hasErrors()) {
            redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.createAccountModel", result);
            redirectAttributes.addFlashAttribute("createAccountModel", createAccountModel);
            return "redirect:/create-account";
        }
        
        // Success case omitted...
    }
}

You can see that we have here a standard spring controller with two methods. The first method serves the GET request for the account creation page and the second one serves the POST request.

What we wanted to achieve is if the POST request is performed and there are validation errors, then the request is redirected to the GET handler with the errors and form field values still populated.

In the POST handler method

if (result.hasErrors()) {
    redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.createAccountModel", result);
    redirectAttributes.addFlashAttribute("createAccountModel", createAccountModel);
    return "redirect:/create-account";
}

In this, we have to populate these to the RedirectAttributes instance.

  • For the BindingResult you have to specify the class with the full package name and contatenate the model’s name to the end.
  • The model must be added with the same name as you were using in this method.

In the GET handler method

@RequestMapping(value = "/create-account", method = RequestMethod.GET)
public String getCreateAccount(Model model) {

  if (!model.containsAttribute("createAccountModel")) {
    model.addAttribute("createAccountModel", new CreateAccountModel());
  } 

  return "create-account/create-account";
}

If we redirect from the POST handler, because we added the flash attributes, the model in the GET handler will be automatically populated with these.

However, if we reach the GET handler with just a regular page request, without anything added as flash attributes, we need to manually add the our form backing object (createAccountModel) to the model.