Friday, January 25, 2013

Annotation based validation with JSR 303 (Bean Validation)

Every time you work with objects, you might want to ensure their state (properties, also referred as collaborators) is valid. So when you build an object based on some given input, you must validate whether this input is appropriate.

JSR 303 (AKA Bean Validation) is an specification defined by the Java Community Process, with the purpose of having an standard mean for validating an object state. 
The idea behind this specification is to use annotations for declaring which restrictions or constraints are applied to an specific object property or even to the whole object. So instead of using some if(...) stuff, you use annotations in the object definition (the class).
As any specification, the community provides a high level design (some documents, interfaces, abstract classes, etc.) and let the vendors implement the details. In the case of JSR 303 there are two main implementations: Apache BVal and Hibernate Validator.

So let's take a peek:

public class Person {

         @NotNull
         private String name;

         ...
}



Pretty fancy, right? With only using a simple annotation, I prevent my object to have a null value in it's property. Well is not like that, but pretty much.
In order to validate the state of my object, an instance of  javax.validation.Validator must be used.

Validating an object


The entry point for obtaining a Validator instance, is the ValidatorFactory class. There are several means to create a ValidatorFactory, but we are going to use the simplest one (and of course, following always the specification) . 

ValidatorFactory  factory = Validation.buildDefaultValidatorFactory();

Validator validator = factory.getValidator();


The Validation class, and in particular the buildDefaultValidatorFactory() method, will look into the classpath for a ValidatorFactory implementation. Because of this, although specific vendor classes will not be directly used in our code, the vendor implementation must be available in the classpath.
With the ValidatorFactory in place, a Validator can be obtained through the getValidator() method. 
Once the Validator is created, any object can be validated:

Person person = new Person("John Doe");
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);

The list of ConstraintViolation indicates whether the object is in a valid state. Of course, if the list is empty, it means there are no errors.
Each ConstraintViolation provides specific information about the error, like the object property where the validation failed, the value erroneously used, etc.



Propagating the validation


In any Object Oriented program, there are object composed by other objects (its collaborators). What if the valid state of a composed object depends on the valid state of its collaborators. 
We could always validate the state of each object before composing them, but we will have to do (and ensure) this every time we construct them. To ensure the dependent object is always correctly built, the @Valid annotation can be used. 

public class Dog{

         @NotNull
         private String name;

         @Valid //Propagates the validation.
         private Person owner;

         ...
}

When a dog instance is validated, the validation is propagated to its owner property. This means, any constraint defined in Person class is evaluated. If a constraint defined in Person is not passed, the Dog validation will fail. 
In the following snippet, a dog validation is performed without explicitly validating the Person used as owner.
  

Person owner = new Person("John Doe");
Dog dog = new Dog("Scooby", owner);
Set<ConstraintViolation<Dog>> constraintViolations = validator.validate(dog);

Custom validations


The specification defines a reduced set of constraints for basic validations. They are very useful in several cases, but they are too generic and sometimes a more specific validation is required.
There are two ways for defining a custom validation: by "extending/aggregating" existing constraints or by implementing a ConstraintValidator.
The simplest way is by defining a new constraint (by creating an annotation) and applying existing constraint:

@Min(18)
@Max(150)
@Constraint(validatedBy = {})
@Documented
@Target({ FIELD })
@Retention(RUNTIME)
public @interface isAdult {

String message() default "Not an adult";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

In the example above the isAdult constraint, aggregates two constraint (Max and Min). So, by using this new constraint in an Integer field, two things are validated: the value  is bigger than 18 and lesser than 150.

The second approach, more powerful and flexible, is to implement a ConstraintValidator. This also involves creating an annotation (for declaring the constraint in a property):

@Constraint(validatedBy = {com.softwaredistilled.validators.MyConstraintValidator})
@Documented
@Target({ FIELD })
@Retention(RUNTIME)
public @interface MyConstraint{

String message() default "Complex message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

And also a class responsible of the specific validation:

public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String>{

  public void initialize(MyConstraint constraintAnnotation)   {

   }


  public boolean isValid(String value, ConstraintValidatorContext context) {

       

       //Custom validation here

    }



}


In the constraint definition, it is indicated what ConstraintValidator to use. The validation mechanism will take care of plumbing and calling the correct methods.

Sometimes yes sometimes not (groups)


You may face the situation where in some scenario you have to validate an object property, but in a different one, you don´t. For this situation the Groups concept can be used.
Let say we are modeling a blog and we have the class BlogPost. This class has a property publishDate that indicates when the post was published. If the post is saved as a draft, this date could be null but at the moment of publishing not.


public class Post{

         @NotNull(group=Publishing.class)
         private Date publishDate;

         ...
}

The group is specified in the constraint, and then used in the validation. Let's say we want to validate if a post is ready  for being published, the group class/interface (Publishing) has to be passed to the validate() method:


BlogPost myPost= new BlogPost ("Something about JSR 303);
myPost.setText("JSR 303 is an specification ... ");

Set<ConstraintViolation<BlogPost>> cviolations = validator.validate(myPost, Publishing.class);


As the publishDate property was not set, the validation will fail (cviolations will have 1 ContraintViolation). If the group class wouldn't be passed, the validation would have succeed, as the publishDate property is validated only if the group is specified.

By using groups, you can define whether a constraint must be evaluated, but this doesn't mean the rest of constraints are not checked. If a validation using groups is performed, not only the constraints with that group are validated but also all constraints with no group specified too.


Conclusions


JSR 303 is an elegant tool for validating objects states, and the better part, it is a JSR. In the majority of cases, the specification is integrated with other frameworks like Spring and Hibernate and not used directly. Or it is used to build some generic library.  
So by using it you get:

  • Clean and concise validation through a declarative approach. Any declarative approach (like transaction demarcation) is always a clean mean of programming for certain specific concerns: do here what you promise me instead of me programming all that.
  • Encapsulation of the validation logic: encapsulating behavior is always a good practice as it leverages components reuse and allows a separated evolution among what is encapsulated and those who use it. With the use of custom validation through CustomValidator, an specific validation logic remains encapsulated and can be reused and changed at any time without affecting any client code. Of course you can achieve validation encapsulation by other means like a simple class, but again this is an standard specification and for some scenarios it always pays back.


The example code


I leave you an example project that uses BVal. It is configured with Maven. You can try changing BVal for Hibernate Validation and, as the specification is always followed, you don't have to change the code at all (that is the whole idea of using JSR's at the end).

Download example

2 comments:

  1. I couldn't find a cleaner explanation on bVal other than yours on web. Thanks!

    ReplyDelete