Spring MVC Form Validation with Annotations Tutorial

This tutorial with full source code will show how to validate a form submission in Spring MVC using JSR-303 annotations. You can grab the code for this tutorial on GitHub if you want to follow along.

One of the most common elements of web applications is validation of user data. Any time a user submits data into your system, it needs to be validated. This is to prevent attacks, bad data, and simple user error. This tutorial will explain the basics of setting up validation with Spring MVC.

You should have a basic understanding of how to submit forms using Spring MVC. If you do not already understand Spring MVC basics, follow some of my other Spring tutorials first.

Let’s begin. To get started, we first need a couple JARs in our classpath. Add the Java validation API and the Hibernate Validator implementation JARs:.

compile 'javax.validation:validation-api:1.1.0.Final'
compile 'org.hibernate:hibernate-validator:5.0.1.Final'
Or, if you’re using Maven:

Next, make sure you activate Spring `annotation-driven`:

Java Configuration:
@EnableWebMVC // this does the trick
public class WebConfig {
  // beans here
XML Configuration:
<mvc:annotation-driven />

Now, we annotate our model object with the constraints we will be validating:

public class Subscriber {

    @Size(min=2, max=30) 
    private String name;
    @NotEmpty @Email
    private String email;
    @NotNull @Min(13) @Max(110)
    private Integer age;
    private String phone;
    private Gender gender;
    @NotNull @Past
    private Date birthday;


All of the validations used above are part of the JSR-303 API, except for “NotEmpty” and “Email”, which are Hibernate-Validator annotations.

Almost there! Now, let’s tell our controller to validate the form in the submission handler method:

public class FormController {
    @RequestMapping(value="form", method=RequestMethod.POST)
    public String submitForm(@Valid Subscriber subscriber, BindingResult result, Model m) {
        if(result.hasErrors()) {
            return "formPage";
        m.addAttribute("message", "Successfully saved person: " + subscriber.toString());
        return "formPage";

Simply adding `@Valid` tells Spring to validate the “Subscriber” object. Nice! Notice we also add a “BindingResult” argument. This is Spring’s object that holds the result of the validation and binding and contains errors that may have occurred. The BindingResult must come right after the model object that is validated or else Spring will fail to validate the object and throw an exception.

When Spring sees “@Valid”, it tries to find the validator for the object being validated. Spring automatically picks up validation annotations if you have “annotation-driven” enabled. Spring then invokes the validator and puts any errors in the BindingResult and adds the BindingResult to the view model.

Now, our view:

<form:form action="/form" modelattribute="subscriber">
    <label for="nameInput">Name: </label>
    <form:input path="name" id="nameInput"></form:input>
    <form:errors path="name" cssclass="error"></form:errors>
    <br />
    <label for="ageInput">Age: </label>
    <form:input path="age" id="ageInput"></form:input>
    <form:errors path="age" cssclass="error"></form:errors>
    <br />
    <label for="phoneInput">Phone: </label>
    <form:input path="phone" id="phoneInput"></form:input>
    <form:errors path="phone" cssclass="error"></form:errors>
    <br />
    <label for="emailInput">Email: </label>
    <form:input path="email" id="emailInput"></form:input>
    <form:errors path="email" cssclass="error"></form:errors>
    <br />
    <label for="birthdayInput">Birthday: </label>
    <form:input path="birthday" id="birthdayInput" placeholder="MM/DD/YYYY">
    <form:errors path="birthday" cssclass="error"></form:errors>
    <br />
    <label for="genderOptions">Gender: </label>
    <form:select path="gender" id="genderOptions">
        <form:option value="">Select Gender</form:option>
        <form:option value="MALE">Male</form:option>
        <form:option value="FEMALE">Female</form:option>
    <form:errors path="gender" cssclass="error"></form:errors>
    <br />
    <label for="newsletterCheckbox">Newsletter? </label>
    <form:checkbox path="receiveNewsletter" id="newsletterCheckbox"></form:checkbox>
    <form:errors path="receiveNewsletter" cssclass="error"></form:errors>
    <br /><br />
    <input type="submit" value="Submit" />

The “form:errors” tag outputs errors associated with the specified path.

Now, if we fire up our app and submit the form blank, we see:


The form also correctly validates that the email address is a valid format, age is  between 13 and 110, the phone number is at least 10 characters long and birthday is in the past.

Cool. But the error messages are terrible! How can we customize them? The easiest (though not best) way is to use the annotations’ “message” property, like so:

@Size(min=10, message="Phone number must be at least 10 characters")

This is nice, but does not support internationalization. Plus, do we really want our messages in our Java objects? Fortunately, we can easily override the default messages in our message bundle. To do this, first set up a message bundle:

public MessageSource messageSource() {
	ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
	return messageSource;
XML Config:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages"/>

Now, create a file called “messages.properties” in “src/main/resources”. Now, we can override the default error messages. Error messages are resolved using the following pattern:


For example, if the age field of our “subscriber” model object fails the “NotNull” validation, the “NotNull.subscriber.age” message would be looked up. If the message isn’t found, “NotNull.subscriber” would be looked for. Finally, if not found, “NotNull” message would be looked for. If that also isn’t found, the default message (what we saw above) would be rendered. With this convention in mind, let’s define our error messages:

Size=the {0} field must be between {2} and {1} characters long
Size.subscriber.name=Name must be between {2} and {1} characters
Size.subscriber.phone=Phone must be at least {2} characters

Min.subscriber.age=You must be older than {1}
Max.subscriber.age= Sorry, you have to be younger than {1}

Email=Email address not valid
Past=Date must be in the past

NotEmpty=Field cannot be left blank
NotNull=Field cannot be left blank

typeMismatch=Invalid format
methodInvocation.myRequest.amount=Invalid format

Notice the use of {0}, {1}, etc. These are arguments that can be passed in to the message. Now, if we run our validations, we should see nicer messages:

spring-mvc-validation-errors-nicer spring-mvc-validation-errors-nicer

Before I sign out, quick usability note that is outside the scope of this tutorial: use front-end validations also! Users expect the convenience of immediately knowing if there is an error, so go ahead and duplicate some logic on the front-end.

That wraps up this tutorial. Check out my next tutorial on creating custom validation annotations to see how to easily create validation annotations to fit your not-so-cookie-cutter situations. I strongly recommend you download the source and run the code. Post any questions you have in the comments below.

Full Source: ZIP, GitHub
To run the code from this tutorial: Must have Gradle installed. Clone from GitHub or download the ZIP. Extract. Open command prompt to extracted location. Run gradle jettyRunWar. Navigate in browser to http://localhost:8080.

Spring 3 Validation Reference

Posted in Spring Tagged with: , , ,
  • Orionix

    Awesome explanation!

    It is really helpful.

    It is also would be great idea if the custom validation case is explained. For example if birthDate shouldn’t be less than certain date, say no less than year of 1900.

    • Steve Hanson

      Glad it was helpful to you! I’ve almost finished a post on custom validation annotations. Should have it up in a few days. Feel free to let me know if there are any other posts you would like to see on here.

  • Arpit Jain

    nyc tutorial

  • Steven Solomon

    How is the mapping between parameters {0}, {1}, {2} and field name, max, min determined? Is it also possible to use named parameters?

    • Steven Solomon

      See https://jira.springsource.org/browse/SPR-6730. The numbered parameters correspond to the fields of the JSR-303 annotations. For @Size, the field it is applied to is {0} (e.g. Subscriber.name in the example), max is {1}, and min is {2}. The order is determined alphabetically. For @Min and @Max, the applied field is {0} and the value is {1}.

  • avatar42

    To properly use custom error messages you will want use the annotation like:

    @Size(min=10, message = “{Size.subscriber.phone}”)

    The brackets surrounding the key are required to make it work which I found unclear in most of the examples I found on the web. Also you will need to link your message source to the validator like so:
    <bean id=”validator” class=”org.springframework.validation.beanvalidation.LocalValidatorFactoryBean”>
    <property name=”validationMessageSource” ref=”messageSource” />
    <mvc:annotation-driven validator=”validator” />

  • kiduxa

    I have done what you said, specially I’m using @Email annotation, but when I submit the form I get an exception from hibernate

    List of constraint violations:[

    ConstraintViolationImpl{interpolatedMessage=’not a well-formed email address’, propertyPath=correo, rootBeanClass=class com.blah.baseProject.database.model.Usuario, messageTemplate='{org.hibernate.validator.constraints.Email.message}’}

    any clue?

    Nice tutorial btw.

  • http://elnibs.wordpress.com/ elnibs

    Just a typo groupId and artifactId (upper case), thx for the great tutorials

    • Steve Hanson

      Fixed. Thanks @elnibs, glad you are enjoying them!

  • sinak1

    I get multiple errors for the validation part.

    The first one is for Integer validation for “age” field. It says something like:
    “javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Integer…..”

    I’ve made sure age is of type Integer and not int.

    the second one I get is for Date validation:
    “Cannot convert from java.lang.String to required type java.util.Date for property birthday;”
    nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type”

    Any solutions for these? Could I possibly have missed something?

    I also noticed when running the app on built in jetty engine the Hibernate part would complain about missing slf4j jar files. So I fixed it by adding the following to build.gradle in “dependencies” section:

    compile ‘org.slf4j:slf4j-api:1.6.4’


    • Sharad Pawar

      add following dependency in your pom.xml







  • Diogo Miranda

    Great explanation Steve, is it possible validate a Calendar attribute?

  • Guido Celada

    check the case in modelAttribute for form:form

  • Arun

    How can i validate a field which can be empty or if given proper value(like email or mobile number) how to go with that

  • Stan S. Stanman

    Great tutorial, very helpful! I particularly like that you’ve included both JavaConfig and XML config – I use JavaConfig exclusively and so many sites provide only the XML config.

  • Vikas Gs

    Hi Steve, Nice post !!

    I tried to follow the steps shown in your example, but the form validation just does not happen !

    The value “result.hasErrors()” is seen as false.
    Any suggestions?

  • Sasha

    why do we need both hibernate validator and javax validator ?

  • Alabama Mothman

    Is there any way to put the (for example) @size min and max in a config bean?

    For example: (

  • Sharad Pawar

    Facing Error at Gender member variable in model class

  • Praveen Banthia

    This does not have logic to not submit if there are any validation errors right ?

  • firstpostcommenter


  • Ovidiu

    You saved me, God bless you !!!


Follow Me

RSS FeedFollow me on Twitter