A look at Bean Validation API scenarios.

Recently I had to start up a new Java project and opted to use the new Java EE Bean Validation, specifically the Hibernate Validation implementation. We’ll also be using it in the context of a Spring 3 application which will give us some extra support for triggering and handling validations.

First add your maven dependency, this is the lasted version implementing the 1.0 spec.

	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-validator</artifactId>
		<version>3.4.1.Final</version> 
	</dependency>

Next in our Spring Configuration class

@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
	/**
	 * Registering our validator with spring mvc for our i18n support
	 */
	@Override
	public Validator getValidator() {
		try {
			return validator();
		} catch (final Exception e) {
			throw new BeanInitializationException(
					"exception when registering validator in "
							+ this.getClass().getName(), e);
		}
	}

	/**
	 * We use springs reloadable message resource and set it to refresh every
	 * hour.
	 * 
	 * @return
	 */
	@Bean
	public MessageSource messageSource() {
		final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
		messageSource.setBasenames("/WEB-INF/messages/validation",
				"/WEB-INF/messages/text", "/WEB-INF/messages/label",
				"/WEB-INF/messages/popover");
		messageSource.setCacheSeconds(3600);
		return messageSource;
	}

	/**
	 * This is the validator we will provide to spring mvc to handle message
	 * translation for the bean validation api (hibernate-validation)
	 * 
	 * @return
	 * @throws Exception
	 */
	@Bean
	public LocalValidatorFactoryBean validator() throws Exception {
		final LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
		bean.setValidationMessageSource(messageSource());
		bean.setProviderClass(HibernateValidator.class);
		return bean;
	}
}

Here we are creating a factory bean with HibernateValidator as the provider class, and we’ve passed in our Spring messageSource bean to handle error message resolving. Finally we register the validator with spring through its WebMvcConfigurerAdapter interface and overriding its getValidator method. Now we are ready to start validating our data.

First lets take a look at our input objects and then we’ll break down whats going on.

@GroupSequenceProvider(CapsAccountFormGroupSequenceProvider.class)
public class CapsAccountForm {
	@NotNull(message = "{required}")
	private PaymentType paymentType;

	@Valid
	private BankAccount bankAccount;

	@Valid
	private AlternateContact alternateContact;

	@AssertTrue(message = "{please.agree}")
	@NotNull(message = "{required}")
	private Boolean agreement;
	//other getters and setters
public final class BankAccount {
	@BankABANumber
	private String bankAccountNumber;

	@NotBlank(message = "{required}")
	@Size(max = 17, message = "{max.seventeen.digits}")
	@Pattern(regexp = "[0-9]*", message = "{numeric.only}")
	private String checkingAccountNumber;

	@NotBlank(message = "{required}")
	@Length(max = 40, message = "{length.exceeded}")
	private String bankName;

	@Length(max = 28, message = "{length.exceeded}")
	private String city;

	private State state;

	@Pattern(regexp = "[0-9]{5}|[0-9]{9}", message = "{zip.length.exceeded}")
	private String zipCode;

	@Pattern(regexp = "[0-9]*", message = "{numeric.only}")
	@Length(max = 10, message = "{length.exceeded}")
	private String phoneNumber;

	@Pattern(regexp = "[0-9]*", message = "{numeric.only}")
	@Length(max = 10, message = "{length.exceeded}")
	private String faxNumber;

	@Length(max = 40, message = "{length.exceeded}")
	private String bankAddress;

	//getters and setters
}
public final class AlternateContact {
	@Length(max = 100, message = "{length.exceeded}")
	public String name;

	@Length(max = 20, message = "{length.exceeded}")
	@Pattern(regexp = "[0-9]*", message = "{numeric.only}")
	public String phone;
	//getters and setters.

Now the above code is used for the following business scenario. First off we are writing to a legacy table to support automation of a current multi step process in a legacy system, so most of our constraints must follow what their tables have currently defined (that should explain some of the random field length checks). Next, the original form has the ability to have two different paymentType options which then affect if you need to populate the bankAccount form or not. If the paymentType is D (Debit) then we need the bankAccount form and process its validations, and if the paymentType is C (Trust, legacy code) then we don’t need to include the bankAccount form and will not process its validations. Finally there is the alternateContact form which is optional and the agreement field which is a required to be checked/True.

First we have the basic validation annotations provided to us by bean validation and hibernate validator. Ones provided by the spec are common to all implementations and hibernate provides a few extra implementation specific ones. I’d say these are mostly self explanatory but we can take a quick look at a few.

All the annotations take a message parameter which if wrapped in curly braces will be evaluated through the spring messageSource otherwise the text given will be the message returned.

The most commonly used annotations will be the @NotNull, @NotBlank (for Strings), @NotEmpty (for Collections) @Pattern, @Length, and @Size. Each of these annotations take various parameters to be used during evaluation, for example the regex field on @Pattern is the regex pattern that the string must match to be considered valid.

Next lets take a look at how our validations are triggered and how the current Spring validation integrates.

The @Valid annotation is used to trigger the bean validation. This will generally be applied to a Spring Controller method parameters. It can also be used to trigger nested evaluations objects like we have in the above code to validate the bankAccount and alternateContact fields. Let’s take a look at the create controller method for our form.

	@RequestMapping(value = "/crids/{crid}/caps", method = RequestMethod.POST)
	public String createCaps(@PathVariable final String crid,
			@Valid @ModelAttribute final CapsAccountForm capsAccountForm,
			final BindingResult errors, final Model model,
			final RedirectAttributes redirectAttributes)

As we said above, user information is pulled from the current user and company info is pulled from the url (crid PathVariable above). Here you can see our CapsAccountForm as a parameter and it annotated with @Valid. When used in a spring controller like this any validations that are triggered will automatically be populated in the BindingResult object for us. If you’ve used spring before you know that in the past you generally implemented the spring interface and populated the BindingResults object on your own.

What if we want to use this same form, but for a restful service instead of a web form? Let’s take a look at our API’s creation endpoint.

	@RequestMapping(value = "/api/crids/{crid}/caps/", consumes = {
			"application/json", "application/xml" }, produces = {
			"application/json", "application/xml" }, method = RequestMethod.POST)
	public @ResponseBody
	CreateCapsAccountOutput createCaps(@PathVariable final String crid,
			@Valid @RequestBody final CapsAccountForm capsAccountForm ) {

As you can see this method is very similar to the previous one, with the exceptions of we’ll be accepting Json/Xml as the input and producing the same. Again the @Valid annotation will trigger and validations on our objects, but this time we have no BindingResult object to be populated, so what will happen this time? Instead it will throw a MethodArgumentNotValidException which we can then handle in a spring ExceptionHandler method.


	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseStatus(value = HttpStatus.BAD_REQUEST)
	public @ResponseBody
	HttpValidationMessage validation(final MethodArgumentNotValidException e,
			final Locale locale) {
		return new HttpValidationMessage(e.getBindingResult(), messageSource,
				locale);
	}

Here we catch any MethodArgumentNotValidException thrown by the given controller (or any controller if this is a global handler in a @ControllerAdvice class) and we pull out hte bindingResults object that is attached to it to populate a message to be returned with a 400 error code.

Now that we’ve seen the basics, let’s look at creating a new custom constraint. If you noticed on the above bankAccount class we are using the annotation BankABANumber which is a customer constraint a I created. Lets take a look at some of the code it is triggering.

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Constraint(validatedBy = BankABANumberValidator.class)
@Documented
@NotBlank
@Size(min = 9, max = 9)
@Pattern(regexp = "[0-9]*")
public @interface BankABANumber {

	String message() default "{caps.account.aba.number.invalid}";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};
}
public class BankABANumberValidator implements
		ConstraintValidator<BankABANumber, String> {

	private ABANumberCheckDigit abaNumberCheckDigit;

	@Override
	public void initialize(final BankABANumber arg0) {
		abaNumberCheckDigit = new ABANumberCheckDigit();
	}

	@Override
	public boolean isValid(final String arg0,
			final ConstraintValidatorContext arg1) {
		return abaNumberCheckDigit.isValid(arg0);
	}

}

First lets look at the annotation. Here you can see we’ve actually combined a few existing validations and applied the @ReportAsSingleViolation annotation. What this will do is instead of showing a different message for each validation, instead it will only report a single message, which in this case is the default we’ve supplied of {caps.account.aba.number.invalid}. So if the field is blank, incorrect size, not a digit, or not a valid routing # then we’ll see a single error message.

Next since we aren’t only wrapping existing annotations, lets look at how we actually determine the routing number is valid. As you can see on our annotation we are supplying the @Constraint with the BankABANumberValidator class as its validation implementation. This class is an implementation of the provided ConstraintValidator interface which takes generics of the annotation and field type we are validating. From here you can access any fields set on the annotation and the value of the field we are validating. We are using Apache Validation that provides us with the algorithm to validate a routing #. I won’t go into the specifics of this validation, but you can easily look it up. So on top of our standard validations, this code as gets executed during validation and simply returns a boolean.

Note: You are able to use springs aspectj @Configurable injection support to inject spring beans into this class. This would allow for dynamic checks against a database or webservice.

Finally lets take a look at one last scenario. If you remember earlier I said we only evaluate the bankAccount object if the paymentType is set to Debit. Now this scenario is slightly more complicated but definitely useful. Let’s look at the CapsAccountForm class to see what exactly we are doing to achieve this validation setup.

On the CapsAccountForm class we have the following annotation @GroupSequenceProvider(CapsAccountFormGroupSequenceProvider.class). Now lets take a look at the code for this before we get into explaining what is happening.

	public List<Class<?>> getValidationGroups(final CapsAccountForm object) {
		final List<Class<?>> groups = Lists.newArrayList();
		groups.add(CapsAccountForm.class);
		if (object.getPaymentType() != null
				&& object.getPaymentType().equals(TrustPaymentMethod.D)) {
			groups.add(BankAccount.class);
		} else {
			object.setBankAccount(null);
		}
		return groups;
	}

First let’s explain what group sequences are. Each validation annotation can take a group parameter which can be used to evaluate only specific groups, or groups in a specific order so that if an earlier group fails a validation check, then the rest are left unevaluated. If you know beforehand which set of groups you want you can use the @Validated annotation on your spring method and supply it a list of classes/interfaces which correspond to the group values.

Now, since our groups are based off the paymentType field we don’t know our groups up front, so we can use the hibernate specific @GroupSequenceProvider to determine our groups before validation the class. Let’s take a look at our sequence provider CapsAccountFormGroupSequenceProvider.

First you notice we add the CapsAccountForm to our groups, since it expects the class its evaluating to be in its group list. Next we add the BankAccount class to our groups IF our paymentType is D, otherwise we ignore it and throw away anything by setting the field to null.

Now we either have a single group, the CapsAccountForm or two groups the CapsAccountForm and BankAccount. Lets look at the path where we only have the CapsAccountForm group. Since the bankAccount field has @NotNull with the group BankAccount it will only be evaluated if the paymentType is D. We don’t have the BankAccount class in our groups this check is ignored, and the @Valid annotation is no processed since our bankAccount field should be null if the paymentType is C. Now if the paymentType is D, then we add the BankAccount group and make sure the bankAccount field is not null, and then proceed to process the remaining fields in the BankAccount object.

BVal is in its earlier stages and will continue to add more support but hopefully seeing these situations and how we adapt bval to them helps you in the future when validation more complex scenarios.

Leave a comment