Validating HTML forms in Spring using Bean Validation

Validating input is a crucial part to ensure data integrity and proper function of the whole application. JSR-303 also known as Bean Validation is a very convenient way to perform such validation at different levels of the application. In this article I would like to present the most important features of Bean Validation and how to use it to validate HTML forms in Spring.

Built-in constraints

The most important part of Bean Validation are annotations which are used to place constraints on fields, methods or parameters. There are several built-in constraints which are useful in many situations:

@Null Checks whether the value is null.
@NotNull Checks whether the value is not null.
@Min Checks whether the value is not smaller than the specified limit. The value must be of type: long, int, short, byte, their wrappers classes, BigInteger, BigDecimal. Types double and float are not supported.
@Max Similar to @Min but checks whether the value is not higher than the specified limit.
@DecimalMin Similar to @Min but can be also used for strings.
@DecimalMax Similar to @Max but can be also used for strings.
@Digits Checks whether the number of integral and fractional digits of the value are not higher than the specified limit. The supported types are the same as for @DecimalMin
@AssertTrue Checks whether the boolean value is true.
@AssertFalse Checks whether the boolean value is false.
@Past Checks whether the date is in the past.
@Future Checks whether the date is in the future.
@Size Checks whether the size of the string, array, collection or map is within specified limits.
@Pattern Checks whether the string value matches specified regular expression.

In case this is not enough a custom constraint can be defined. The details how to do it are described in Custom bean validation constraints article.

Validation messages

Every validation annotation has message property which can be optionally set to provide a custom message to the user when the validation fails. If it is omitted, default message will be used.

In the following example, we put size constraint on firstName and lastName fields and two different constraint on age field:

@Size(min = 1, max = 20)
private String firstName;
@Size(min = 1, max = 40)
private String lastName;
@Min(value = 1, message = "Age must be positive")
@Max(value = 100, message = "Age must be not higher than 100")
private Integer age;

If the validation of firstName or lastName fails, the default message will be forwarded to a user. Additionally, if validation of age fails, one of the custom messages will be used depending on which constraint failed.

Moving messages to a separate file

Because hard-coding messages is generally not a good idea, we can create file in the default package of the application and place the messages there:

firstNameInvalid=Must be between {min} and {max} characters
lastNameInvalid=Must be between {min} and {max} characters
ageTooLow=Age cannot be smaller than {value}
ageTooHigh=Age cannot be higher than {value}

The values in the curly braces are place-holders for properties of the annotation. Then we can refer to these messages using curly braces:

@Size(min = 1, max = 20, message = "{firstNameInvalid}")
private String firstName;
@Size(min = 1, max = 40, message = "{lastNameInvalid}")
private String lastName;
@Min(value = 1, message = "{ageTooLow}")
@Max(value = 100, message = "{ageTooHigh}")
private Integer age;

Overriding messages for built-in constraints

If you don’t like the default messages for built-in constraints but also don’t want to specify your own message every time, you can override default messages in file as follows:

javax.validation.constraint.Size.message=Field size must be between {min} and {max} characters

Internationalizing validation messages

The idea of keeping validation messages in a separate file has one additional benefit. If you want to provide validation messages for many different languages, you can create one such file per language/region.

For example, German messages can be put into file and Canadian French messages into The lookup of messages works the same as for resource bundles so the file will be checked last if nothing else matches.

Validating in a controller

After we have assigned constraints to the properties of our model attribute and set messages, we can configure controller to handle the validation:

@RequestMapping(method = RequestMethod.POST)
public String add(@ModelAttribute(value = "newPerson") @Valid Person newPerson, BindingResult result, Model model) {
    if (result.hasErrors())
        return "personlist";
    return "redirect:/personlist";

The first thing to notice is that we have added @Valid annotation to our model attribute to perform the validation of newPerson argument. The validation errors (if any) will be stored in the immediately following instance of BindingResults. Then at the start of the method we call hasErrors() method to check if there were any errors. If yes, we show the form again to the user to give him a chance to correct the data.

Presenting validation errors to user

The only thing left is presenting validation errors to a user. The easiest way to do so is to sprinkle several <sf:errors> tags in the view:

<%@ taglib prefix="c" uri="" %>
<%@ taglib prefix="s" uri="" %>
<%@ taglib prefix="sf" uri="" %>
<%@ page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Person List</title>
        <link href="<s:url value='/resources/css/styles.css'/>" rel="stylesheet" type="text/css"/>
            <c:forEach var="person" items="${list}">
                <span class="box-with-border"><c:out value="${person.firstName} ${person.lastName} (${person.age})" /></span>
        <sf:form method="POST" modelAttribute="newPerson" cssClass="personForm">
                    <td><sf:label path="firstName">First name:</sf:label></td>
                    <td><sf:input path="firstName"/></td>
                <tr><td colspan="2"><sf:errors path="firstName" cssClass="errors"/></td></tr>
                    <td><sf:label path="lastName">Last name:</sf:label></td>
                    <td><sf:input path="lastName"/></td>
                <tr><td colspan="2"><sf:errors path="lastName" cssClass="errors"/></td></tr>
                    <td><sf:label path="age">Age:</sf:label></td>
                    <td><sf:input path="age" /></td>
                <tr><td colspan="2"><sf:errors path="age" cssClass="errors"/></td></tr>
                <tr><td></td><td><input type="submit" value="Add" /></td></tr>

The path attribute of the <sf:errors> tag specifies for which HTML form field validation messages should be shown.


Bean Validation provides standard and very convenient method for validating HTML forms in Spring. Additionally, Bean Validation is not limited to checking form input but can be also used in many other situations.

The complete source code for the example can be found at GitHub.


About Robert Piasecki

Husband and father, Java software developer, Linux and open-source fan.
This entry was posted in Java, Spring and tagged , , . Bookmark the permalink.

4 Responses to Validating HTML forms in Spring using Bean Validation

  1. Pingback: Custom bean validation constraints | softwarecave

  2. Omar Gameel says:

    very helpful
    thank you

  3. Alabama Mothman says:

    Is there a way to put this editing inside the servlet.xml or some other file . I personally would never want to add this hardcoding to my programs. I think it’s a horrible idea.

  4. stefanhgraf says:

    Very helpful and complete but I think the built-in messages may have a typo (missing s on constraints).

    Here is an example of translations I have:

    javax.validation.constraints.Size.message=The fields must have between {min} and {max} characters
    javax.validation.constraints.Digits.message=Please enter only digits.
    javax.validation.constraints.DecimalMax.message=Please enter a value at or below {2}.
    javax.validation.constraints.DecimalMin.message=Please enter a value at or above {2}.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s