Spring MVC form validation with Hibernate Validator

Form validation with Spring MVC has not been my favourite part of web development up until now. However, today I was faced with the task yet again and decided to take a look at form validation with JSR-303 and the Hibernate Validator implementation. This article sums up my experiences this far.

As we will see, validation need not be tedious and consist of mile long Validator implementations once we embrace the power of annotations and JSR-303.

The article assumes that you have a correctly configured Spring application context set up with package scanning and annotation driven MVC:

<mvc:annotation-driven />
<context:component-scan base-package="com.codemunchies.jsr303" />

Maven dependencies

First off we need a couple of Maven dependencies in addition to the traditional Spring dependencies. The following three dependencies are added to your POM and you should be good to go:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.2.GA</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.5.11</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.5.11</version>
</dependency>

You should also note that you need to add the JBoss Maven 2 repository to your POM in order for Maven to find the Hibernate Validator dependency.

Our view

We will need a simple view for our form, where the user can enter the information and  submit it. We will use the Spring form taglib to construct our form and bind it to our form class, as shown in the code example below.

<p>
    <form:form action="" method="post" commandName="userForm">
        <div>
            <form:label path="name">Name:</form:label>
            <form:input path="name"/>
            <form:errors path="name" />
        </div>
        <div>
            <form:label path="email">Email:</form:label>
            <form:input path="email" />
            <form:errors path="email" />
        </div>
        <div>
            <input type="submit" value="  OK  "/>
        </div>
    </form:form>
</p>

Controller and form beans

Our controller consists of two methods, one for handling the initial GET request and one for handling the POST request upon form submission. The GET handler simply populates our model before we’re sent on our way to the form view.

@Controller
@RequestMapping("/form.html")
public class FormTestController {

    @RequestMapping(method = RequestMethod.GET)
    public String get(final ModelMap model) {

        UserForm userForm = new UserForm();
        model.addAttribute("userForm", userForm);
        return "form";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String post(final ModelMap model, @Valid final UserForm userForm,
        final BindingResult result) {

        if (result.hasErrors()) {
            return "form";
        }

        return "success";
    }
}

The first interesting thing we see is in the POST handler method, where we have added a @Valid annotation to our form parameter. This indicates that we want this bean validated using the JSR-303 implementation, in this case Hibernate Validator. Whenever a POST request is sent to our controller the validator is invoked and our form bean is validated based on the annotated fields in our UserForm bean.

public class UserForm {

    @NotEmpty
    @Size(max = 20)
    private String name;

    @NotEmpty
    @Email
    private String email;

    // Getters and setters
}

This tells the validator that none of the fields can be empty, that name should not be longer than 20 characters and that email should be a valid e-mail address (The @Email annotation is a Hibernate Validator annotation). Really quite neat!

With the above code, you will now get the following view by summiting an empty form:

Hibernate Validation provides default messages for the constraints, but these can easily be overridden.

As we continue to fill out the form with different values, we see the constraints kick into action and tells us to please fill out the form correctly:

Custom constraints

You can also create your own constraints quite easily. To demonstrate this we will add a list of web site addresses to our form, which will be represented by a list of Website objects in our UserForm.

public class Website {

    @Url
    private String url;

    // Getters and setters
}

Which introduces the following change to our UserForm bean:

@Valid
private List websites = new ArrayList();

The secret here is the custom @Url annotation which we have implemented below:

@Documented
@Constraint(validatedBy = UrlValidator.class)
@Target( { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Url {

    public abstract String message() default "must start with 'http'";

    public abstract Class[] groups() default {};
    public abstract Class[] payload() default {};
}

The important part here is the abstract message() method which holds our default validation message. The UriValidator referenced in the @Constraint annotation is the class actually performing the validation, shown here:

public class UrlValidator implements ConstraintValidator<Url, String> {

    @Override
    public void initialize(final Url target) {
    }

    @Override
    public boolean isValid(final String url,
            final ConstraintValidatorContext context) {

        return url.startsWith("http");
    }
}

In our form view we add the following code to display the website input boxes:

<c:forEach var="website" varStatus="status" items="${userForm.websites}">
    <c:set var="index" value="${status.index}"/>
    <div>
        Website ${index}:
        <input id="websites${index}.url" name="websites[${index}].url" value="${userForm.websites[index].url}"/>
        <form:errors path="websites[${index}].url" />
    </div>
</c:forEach>

These changes results in the following view when we refresh our page and try to enter a web site address:

Entering valid information in all fields lets us pass through the POST handler method in our controller and we’re ready to move on with valid data!

Compared to the old Spring validator pattern which I’ve been using over the last years, this new way of validating forms is a breath of fresh air. Basic form validation is up and running in no time with a few annotations and you have the flexibility to perform more complex validation whenever the need arises. The only issue that I currently see (but which I have not thoroughly tested) is testing, which may be a bit more complicated due to the heavy use of annotations. But that is perhaps a topic for another article.

Sources of inspiration

Front page image by brian.gratwicke // http://www.flickr.com/photos/briangratwicke/2725343507/

About Jaran Nilsen

I've been working as a lead developer at Integrasco AS since it's startup in 2004. My interests in software development are ranging from the lowest bit to design patterns and development methods. On a daily basis my main area of responsibility is data mining technology as well as a bit of team management. In addition to my work at Integrasco AS, I am also running an open source project called iTunes Agent over on Sourceforge, which provides non-iPod owners with a way to transfer music from iTunes to their media player.