Simple forms with Spring MVC 2.5

by Tom van ZummerenMay 14, 2009

Having used the Spring MVC 2.5 webframework for a while now, I felt the urge to blog about some of the experiences I had with it. Especially about the part concerning forms, validation, binding and property editors. That’s the part that was usually provided to you by the SimpleFormController (in the ‘old’ Spring MVC). But now, in Spring MVC 2.5, where do you start? You don’t have a convenient base class anymore that tells you what methods you can override. Instead you have a bunch of annotations and a lot of magic.

Just to clarify, I love Spring MVC 2.5. It provides much more freedom and reduces the amount of controller classes you need to write, compared to previous versions of the framework. But you need to follow some best practices to avoid making a mess of your controller classes.

Return types

With your first glance at Spring MVC 2.5 you have probably noticed that controller methods don’t have to return a ModelAndView anymore. Instead, controllers are allowed to return nothing (void), a String representing a view, or just a model. This creates a lot of freedom, but in my opinion can also cause some inconsistency and unclarity. By convention, if your controller does not explicitly return a view, the view name is defined by converting the mapped URL into a view name. This will save you some code, but you have to remember this convention to know what’s going on.

You should be careful not to mix the return types of all your controller methods. I suggest picking a return type and sticking with it. In my case I picked the old fashioned ModelAndView since it can contain both a view and a model, which works in all cases. This makes your controller methods consistent and makes it more clear what to expect from those methods.

Forms & binding

To create a very basic form you basically only need to handle a request to view the form and to submit the form. Let’s say we have the following class:

public class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Instances of this class will get manipulated by the form controller we will create. The SimpleFormController used to call an instance of this class the “form backing object”. Let’s keep calling it this way. Implementing this very basic form in Spring MVC 2.5 would typically look like this:

@Controller
@RequestMapping(“/userForm.html”)
public class UserFormController {

    @RequestMapping(RequestMethod.GET)
    public ModelAndView show() {
        return new ModelAndView(“userForm”, “user”, new User());
    }

    @RequestMapping(RequestMethod.POST)
    public ModelAndView submit(User user) {
        // Do something with the submitted User
        return new ModelAndView(new RedirectView(“/userForm.html”));
    }
}

Notice that you don’t need to specify a command class. Instead, you only have to specify the class as an argument in the submit method and request binding will automatically occur.

Custom form backing object

In a lot of cases, you will want to create and initialize the form backing object manually. In the previous example, a new instance of User is automatically created when the submit occurs. With the old SimpleFormController, creating your own form backing object could be done by overriding the formBackingObject() method. In Spring MVC 2.5, you can achieve the same behavior by specifying the @ModelAttribute annotation on the method responsible for creating that object.

@ModelAttribute(“user”)
public User formBackingObject() {
    return new User();
}

This method can also specify all kinds of arguments just like a normal controller handler method. So you could, for example, do the following:

@ModelAttribute(“user”)
public User formBackingObject(Long id) {
    if (id != null) {
        return userDao.findById(id);
    }
    return new User();
}

This makes it possible to use the same form controller for both adding and editing existing instances of the User object. Having a @ModelAttribute annotated method in your controller class basically tells Spring MVC to include whatever is returned by that method to every request handled within that controller class.

To make sure your submit method uses the same instance, you have to annotate its argument with @ModelAttribute as well.

@RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(@ModelAttribute(“user”) User user) {
    // Do something with the submitted User
    return new ModelAndView(new RedirectView(“/userForm.html”));
}

When a submit request comes in, first our formBackingObject() method is called. Second, request binding occurs on this object. And finally this object is passed to the submit() method. So the object passed to submit() is the same instance as the one created by formBackingObject().

Handling bind errors

By default, Spring MVC 2.5 throws an exception when errors occur during request binding. You usually don’t want that. Instead you want to handle those errors yourself by presenting these errors to the user.

To do this, you have to define a BindingResult argument in the submit() method. This argument has to be right after the object on which request parameters are bound to.

public ModelAndView submit(@ModelAttribute(“user”) User user, BindingResult bindingResult)

For the exceptional case that you are binding to multiple objects, you can specify a BindingResult for each one of them like this:

public ModelAndView submit(User user, BindingResult userBindingResult,
Credentials credentials, BindingResult credentialsBindingResult)

So now what happens is that no exception is thrown when bind errors occur. Instead, errors are registered on the BindingResult that is passed to your submit method. You can now decide what to do with the errors. In most cases you only want to cancel the submit and return to the form view which displays the errors to the user. A simple if statement can do the trick.

@RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(@ModelAttribute(“user”) User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return new ModelAndView(“formView”);
    }
    // Do something with the submitted User
    return new ModelAndView(new RedirectView(“/userForm.html”));
}

To display the errors in your JSP view (in this case called “formView”) you can simply use the Spring form taglib like you did using the old Spring MVC.

Validation

With SimpleFormController the only thing you needed to do for validation is write your own Validator class and inject it into the SimpleFormController bean. With Spring MVC 2.5 you can still use that same Validator class and inject it into your controller, but now you have to call it manually.

@RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(@ModelAttribute(“user”) User user, BindingResult bindingResult) {
    userValidator.validate(user, bindingResult);
    if (bindingResult.hasErrors()) {
        return new ModelAndView(“formView”);
    }
    // Do something with the submitted User
    return new ModelAndView(new RedirectView(“/userForm.html”));
}

You pass your validator the same BindingResult that holds the binding errors. The validator should register any errors on this object as well.

Property editors

Finally, I want to explain how property editors work in Spring MVC 2.5. Property editors are used by the request binder to help converting String values to other types (like a String to a Date or to a User instance). Actually, nothing has changed here except you have to specify the @InitBinder annotation instead of overriding the initBinder() method on SimpleFormController.

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(new CustomDateEditor(“dd/MM/yyyy”, true));
    // Register any property editor you want
}

The property editors you register here are used for all binding operations performed within the same controller class.

Conclusion

Spring MVC 2.5 provides an elegant approach to handle form related requests, but you just have to know how exactly.

I hope you found this information useful. Please leave a comment in case you have questions about this topic, or want to share your experiences with Spring MVC.