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
@Email 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.