I always love it when I can use other people’s work. (Legally, of course.) Such is the case with JSR-303 validations, the reference implementation for which is Hibernate Validator. When combined with Spring’s conversion services, this makes for a lot less work in validating input in web applications.
Let’s look at a simple case – a login form containing an email address and password. Obviously, Spring and Hibernate aren’t going to be able to automatically validate that an email and password are correct (or, at least, not in this case), but they can validate that the fields were filled in when the form was submitted.
Let’s start off with a little Data Transfer Object (DTO) to represent the contents of the login form:
class LoginDTO { @NotEmpty private String email; @NotEmpty private String password; public LoginDTO() { } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Notice that the fields are annotated with @NotEmpty
. This is a Hibernate-provided (non-JSR-303) annotation that combines “not null,” “at least 1 character long” and “not all white space.” Very handy. Now all we have to do is define the appropriate method within our Spring @Controller
-annotated class:
@RequestMapping(value = "/login", method = RequestMethod.POST) public String postLoginForm(@ModelAttribute("login") @Valid LoginDTO loginDto, BindingResult binding) { if (binding.hasErrors()) { return VIEW_LOGIN; } ...
The @Valid
annotation causes Spring to automatically run the validation tests defined in LoginDTO
on the form input after it has converted the input parameters to an instance of LoginDTO
. If there are errors, in this case, we re-render the view. The BindingResult
will be available to the view, allowing the errors to be displayed using Spring’s form
tags.
All well and good. But what if we want to customize the error message thats displayed? JSR-303 has support for this, but when first tried to use it, I got tripped up a bit.
The way you override the default message is by modifying the annotations in LoginDTO
. What I originally tried was:
@NotEmpty(message="login.error.emailRequired") private String email; @NotEmpty(message="login.error.passwordRequired") private String password;
This didn’t work – I wasn’t doing it right. In the past, I’ve used the Struts validation system, which is similarly annotation-based allowed you to provide message codes like this. Struts, however, had two different annotation parameters, one which provided a full text message, and the other which allowed you to provide a message key that would be looked up via a message bundle. In Spring, however, there’s only one parameter, which does double duty. The way I used it above, the output error text was, you guessed it, “login.error.emailRequired” or “login.error.passwordRequired”. Not what I wanted. To get Spring to use the string as a message key, you have to include it in braces so:
@NotEmpty(message="{login.error.emailRequired}") private String email; @NotEmpty(message="{login.error.passwordRequired}") private String password;
The braces are a signal to the validation system that the contents should be “interpolated” – looked up in the message bundle, in this case.
The validation system has support for passing in parameters – for example, the @Size
annotation takes parameters:
@Size(min=1,max=5) private String shortString;
When providing a message, you can use {min}
and {max}
inside the string in order to get more informative messages:
@Size(min=1,max=5,message="The length must be between {min} and {max}") private String shortString;
Essentially, if you want text looked up, you do it the same way. Hence the need for the braces surrounding the message key. It helps if you read the documentation carefully.
The validation system has built-in messages for the various validations it provides. You can override these “base” messages in your message bundle or, as I said, provide your own custom messages on an annotation-by-annotation basis. By default, the validation system will look for a resource bundle named ValidationMessages
from which to obtain your text. Thus, a common approach is to place these in a ValidationMessages.properties
(and I18 equivalents) in the root of your classpath. (src/main/resources
for Maven people).
As it happens, I’m not terribly fond of splitting my text between multiple files – I’d rather have the messages be located in the same resource bundle I’m using for other messages via Spring. If you go searching on the Internet, you’ll see people talking about providing custom MessageInterpolator
classes. That’s the “old” way – fortunately, Spring has made it a lot easier in the most recent versions of Spring MVC.
I’m typically configuring Spring MVC in Java, rather than XML. To make Spring and the validation framework serve messages out of the same file, include the following in the class used to configure Spring MVC:
@Bean(name = "messageSource") public MessageSource messageSource() { ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource(); bean.setBasename("classpath:text"); bean.setDefaultEncoding("UTF-8"); return bean; } @Bean(name = "validator") public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setValidationMessageSource(messageSource()); return bean; } @Override public Validator getValidator() { return validator(); }
The first method defines the messageSource
bean to Spring as a whole. In this particular case, I’m using a resource bundle named “text”. (i.e. text.properties
). The second method defines a validator
bean which uses our Spring messageSource
for validation messages. LocalValidatorFactoryBean
is the default implementation Spring uses – we just want to give it a non-default message source. The final method overloads the getValidator
method from WebMvcConfigurerAdapter
and returns the bean built by the second method. Voila! One resource bundle to rule them all…
As an aside, a quirk I’ve noticed. Spring supports both a ReloadableResourceBundleMessageSource
and ResourceBundleMessageSource
class. The “basename” parameter of the two don’t seem to follow exactly the same conventions – when you use the ReloadableResourceBundleMessageSource
class, the classpath:
seems to be required in the basename
, while if you use just ResourceBundleMessageSource
, you don’t want to have it there. I haven’t dug into the reasons for the differences, but there you are. (I use the reloadable version because it allows me to fiddle with the messages at runtime as I’m working on JSP’s.)
If you want to globally override the default text provided by Hibernate, here are the message keys:
Annotation | Message Key | Default Text |
---|---|---|
@AssertFalse | javax.validation.constraints.AssertFalse.message | must be false |
@AssertTrue | javax.validation.constraints.AssertTrue.message | must be true |
@DecimalMax | javax.validation.constraints.DecimalMax.message | must be less than or equal to {value} |
@DecimalMin | javax.validation.constraints.DecimalMin.message | must be greater than or equal to {value} |
@Digits | javax.validation.constraints.Digits.message | numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected) |
@Future | javax.validation.constraints.Future.message | must be in the future |
@Max | javax.validation.constraints.Max.message | must be less than or equal to {value} |
@Min | javax.validation.constraints.Min.message | must be greater than or equal to {value} |
@NotNull | javax.validation.constraints.NotNull.message | may not be null |
@Null | javax.validation.constraints.Null.message | must be null |
@Past | javax.validation.constraints.Past.message | must be in the past |
@Pattern | javax.validation.constraints.Pattern.message | must match “{regexp}” |
@Size | javax.validation.constraints.Size.message | size must be between {min} and {max} |
@CreditCardNumber | org.hibernate.validator.constraints.CreditCardNumber.message | invalid credit card number |
org.hibernate.validator.constraints.Email.message | not a well-formed email address | |
@Length | org.hibernate.validator.constraints.Length.message | length must be between {min} and {max} |
@NotBlank | org.hibernate.validator.constraints.NotBlank.message | may not be empty |
@NotEmpty | org.hibernate.validator.constraints.NotEmpty.message | may not be empty |
@Range | org.hibernate.validator.constraints.Range.message | must be between {min} and {max} |
@SafeHtml | org.hibernate.validator.constraints.SafeHtml.message | may have unsafe html content |
@ScriptAssert | org.hibernate.validator.constraints.ScriptAssert.message | script expression “{script}” didn’t evaluate to true |
@URL | org.hibernate.validator.constraints.URL.message | must be a valid URL |
Keys that begin with javax.validation
are for annotations defined in JSR-303, while those that start with org.hibernate
are for additional annotations provided by Hibernate Validator.
Note that the table above is for Hibernate Validator 4.3.0.Final. Hibernate Validator 5.x is based on the new JSR-349 – the 1.1 version of Bean Validation, and is in Candidate Release state as I write this – I haven’t yet taken the time to see what additional features it provides.