April 14th, 2010

javax.validation integration with Spring 3.0 MVC is cool

Spring 3.0 includes some great features and one of my favourites is its integration with the JSR 303 bean validation API.  It uses the method-level validation proposal from the specification.  Note the word proposal: Method-level @Valid annotations are mentioned in an appendix rather than in the official part of the specification.  Basically you annotate the form bean parameter with @Valid and include a BindingResult paramater on your handler method and Spring validates the bean after binding, populating the BindingResult instance with any errors that occur.

    @RequestMapping(method = RequestMethod.POST)
    public String saveForm(@Valid MyForm myForm, BindingResult result) {
        if (result.hasErrors()) {
            // usually return the user back to the form
        }
        // process the form
    }

You obviously also need to annotate you form bean class with JSR 303 annotations.

What about multi page (wizard style) forms?

All this works great with one page forms but what happens when your form spans more than one page?  Validation and other Spring MVC 3.0 features work great for single page forms but there’s not much out of the box support wizard style forms yet.  These types of forms are very common for checkout style processes and forms where more complex interaction is required, for example, where a user is asked different sets of questions based on their earlier answers.

I’ve been working on an online donation, multi page, wizard and wanted to use the new Spring 3.0 MVC style rather than the old Spring 2.0 AbstractWizardFormController.  I’ll probably post more about my solution later but it involved using a HTTP request parameter to specify the page, a handler method per page in my controller and the params parameter of the @RequestMapping annotation to help Spring select the correct method.

    @RequestMapping(method = RequestMethod.POST, params = "page=1")
    public String handlePage1(MyForm myForm, BindingResult result) {
        // what about validation?
    }

Spring 3.0 integration with JSR 303 validation doesn’t work with multi page forms

My requirements (like most multi page forms) is to validate the data submitted by the user on the submission of the page where they entered it.  If I annotate my form bean method parameter with @Valid then it validates the whole bean and there’s currently no way round this.  This won’t work: mandatory fields on page 2 of the form won’t be filled in when the user submits page 1.

JSR 303 validation does have the concept of groups.  You can specify which groups each validation annotation applies to and map these groups to your various pages:

@GroupSequence({MyForm.class, MyForm.Page1.class, MyForm.Page2.class})
public class MyForm {
    public static interface Page1 {}
    public static interface Page2 {}

    @NotNull(groups = {Page1.class})
    private String page1MandatoryField;

    @NotNull(groups = {Page2.class})
    private String page2MandatoryField;

    ...
}

For real applications you’re better naming your groups based on what’s on the page, rather than its number.

JSR 303 then provides mechanisms for you to validate only a specify group or groups on a bean (great!) but there’s no way of integrating this with Spring MVC binding step (not great!).

Extend JSR 303 @Valid annotation to specify which group to validate

I can work round this pain by manually calling the group validation step in my handler methods.  This is exactly the kind of boiler plate repetitive code Spring is usually so good at doing for you. What I’d really like is to be able specify which groups I want Spring to validate using an annotation.  The most elegant solution I believe would be to extend the @Valid annotation to include a groups parameter:

    @RequestMapping(method = RequestMethod.POST, params = "page=1")
    public String handlePage1(@Valid(groups = {Page1.class}) MyForm myForm,
            BindingResult result) {
        if (result.hasErrors()) {
            return "page1";
        }
        return "page2";
}

The @Valid annotation primary purpose is for cascading validation down the object graph. I believe a groups parameter would also be useful for this purpose but don’t have a concrete example of this.

Obviously extending the specification and implementation the solution is out of Spring’s hands so in the interim maybe Spring could provide a @ValidationGroups parameter to do the job for you:

    @RequestMapping(method = RequestMethod.POST, params = "page=1")
    public String handlePage1(@Valid @ValidationGroups(Page1.class) MyForm myForm,
            BindingResult result) {
        if (result.hasErrors()) {
            return "page1";
        }
        return "page2";
}

There’s already a Spring feature request asking for something along these lines.   Please go and vote for it!