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 ValidationMessages.properties 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 ValidationMessages.properties 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 ValidationMessages_de.properties file and Canadian French messages into ValidationMessages_fr_CA.properties. The lookup of messages works the same as for resource bundles so the file ValidationMessages.properties 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"; list.addPerson(newPerson); 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="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="s" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %> <%@ page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <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"/> </head> <body> <p> <c:forEach var="person" items="${list}"> <span class="box-with-border"><c:out value="${person.firstName} ${person.lastName} (${person.age})" /></span> </c:forEach> </p> <sf:form method="POST" modelAttribute="newPerson" cssClass="personForm"> <table> <tr> <td><sf:label path="firstName">First name:</sf:label></td> <td><sf:input path="firstName"/></td> </tr> <tr><td colspan="2"><sf:errors path="firstName" cssClass="errors"/></td></tr> <tr> <td><sf:label path="lastName">Last name:</sf:label></td> <td><sf:input path="lastName"/></td> </tr> <tr><td colspan="2"><sf:errors path="lastName" cssClass="errors"/></td></tr> <tr> <td><sf:label path="age">Age:</sf:label></td> <td><sf:input path="age" /></td> </tr> <tr><td colspan="2"><sf:errors path="age" cssClass="errors"/></td></tr> <tr><td></td><td><input type="submit" value="Add" /></td></tr> </table> </sf:form> </body> </html>
The path attribute of the <sf:errors> tag specifies for which HTML form field validation messages should be shown.
Conclusion
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.
Pingback: Custom bean validation constraints | softwarecave
very helpful
thank you
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.
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}.