Update (february 17, 2009): With the first alpha release of Hibernate Validator 4.0 the JSR 303 reference implementation some of the steps described here are not neccessary any more. In particular you can retrieve the project from the Maven repo at JBoss now. This post is also the basis for the "Getting started" section of the reference guide which is part of the Hibernate Validator distribution.
Validating objects against different criteria is a requirement in basically every software development project. Examples are countless:
- a given object might never be allowed to be null
- a number could be required to be always in the interval [1 ... 100]
- a String might be expected to match some regular expression or to represent a valid e-mail address, credit card number, license plate etc.
Where Java is concerned, a couple of dedicated validation frameworks have been around for quite a long time. The probably best-known ones are Commons Validator from the Apache Commons project and Hibernate Validator. Other options are iScreen and OVal, and the ubiquitous Spring framework comes with its own validation package, too.
With JSR 303: "Bean Validation" efforts are under-way, to establish a standard validation API. The JSR tries to combine the best features of the different validation frameworks, while making you independent from any concrete implementation.
Try it out yourself
Though the JSR 303 specification is not yet completed, you can already get a first impression, what it will feel like to use the standardized validation API. At the time of writing I am aware of two projects that aim for an implementation of the JSR 303 spec (I'd be happy to learn on any other implementations):
- The reference implementation, which is based on Hibernate validation and developed at JBoss, mainly by the people behind the spec
- agimatec validation, based on a validation framework by the German software company agimatec GmbH
In the following I will describe the first steps using the reference implementation. Help on using agimatec-validation can be found here.
Download and build the JSR 303 RI
The reference implementation comes in form of a Maven project. As I didn't find it in any public Maven repository, I checked it out from SVN at JBoss:
1 |
svn co http://anonsvn.jboss.org/repos/hibernate/validator/trunk/ |
The checked-out project has now to be built using Maven. As of January, 27th (repo revision #15824), unfortunately one of the four contained sub-projects hibernate-validator doesn't build properly, as the version of one of its dependencies is not specified in the project's pom.xml.
So I added the version of the tck-utils dependency in hibernate-validator/pom.xml. Furthermore, I specified the versions of two other dependencies slf4j-api and slf4j-log4j12 in the same file. Failure to do so gave me NoClassDefFoundErrors within my own project, since these libraries weren't added as transitive dependencies to my classpath. The three modified dependencies in hibernate-validator/pom.xml should look like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>tck-utils</artifactId> <version>0.9-SNAPSHOT</version> <scope>test</scope> </dependency> ... |
Use Bean Validation in your own project
Having built the JSR 303 API and the RI, it's time to make use of it in a litte sample project. First create a new Maven project using the Maven archetype plugin:
1 |
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId= -DartifactId=beansvalidation-test -Dversion=1.0-SNAPSHOT |
Now add the dependencies to the Bean Validation API and the reference implementation to your project's pom.xml. Also make sure since JSR 303 is all about annotations that you are using at least Java 5 as source/target compliance level. Having done so, your POM should look as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.gm</groupId> <artifactId>beans-validation-test</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.5</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>RELEASE</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> </project> |
Open the project in the IDE of your choice and create the following class as an exemplary domain object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package org.gm.beansvalidation; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; public Car(String manufacturer, String licencePlate) { super(); this.manufacturer = manufacturer; this.licensePlate = licencePlate; } //getters and setters ... } |
@NotNull and @Size are so-called constraint-annotions, that we use to declare certain constraints, which shall be applied to the fields manufacturer (shall never be null) and licensePlate (shall never be null and must be between 2 and 14 characters long).
To perform a validation of these constraints, we use the Validator interface defined by the specification. Let's try it in a test for our Car class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package org.gm.beansvalidation; import static org.junit.Assert.*; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.junit.BeforeClass; import org.junit.Test; public class CarTest { private static Validator validator; @BeforeClass public static void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void testManufacturerIsNull() { Car car = new Car(null, "DD-AB-123"); Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car); assertEquals(1, constraintViolations.size()); assertEquals( "may not be null", constraintViolations.iterator().next().getInterpolatedMessage()); } @Test public void testLicensePlateTooShort() { Car car = new Car("Audi", "D"); Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car); assertEquals(1, constraintViolations.size()); assertEquals( "size must be between 2 and 14", constraintViolations.iterator().next().getInterpolatedMessage()); } @Test public void testCarIsValid() { Car car = new Car("Audi", "DD-AB-123"); Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car); assertEquals(0, constraintViolations.size()); } } |
On line 22 we get a Validator object from the ValidatorFactory. Then, we use this validator to validate the car object. The validate() method returns a set of ConstraintViolation objects, which we can iterate through in order to see which validation errors occured.
The first two validate() calls return a violation of the @NotNull constraint on manufacturer (line 31) and of the @Size constraint on licensePlate (line 46). If the object could be validated successfully (as in line 59), validate() returns an empty set.
Note that we only use classes from the package javax.validation, which stems from the Bean Validation standard API. The concrete implementations, e.g. for ValidatorFactory and Validator, are hidden from us. Therefore it would be no problem to switch to another implementation of the API, should that need arise.
What's next?
This post showed the very first steps using the Bean Validation API and its reference implementation. As the specification is not yet completed, it wouldn't be wise to make use of it already in a real-world application. The API still experiences frequent changes, things that work today, might end up in a compiler error tomorrow due to a renamed interface for instance.
The JSR is right now in "Public Review" status, meaning that everyone is invited to have a look on the specification and comment on it in the JSR 303 feedback forum.
Though the reference implementation doesn't implement the spec completely (e.g. specified annotations as @Min, @Max or @AssertTrue are not yet supported, using them will result in a NullPointerException), it still allows us to get a practical impression where the path of bean validation will lead one day.
In a subsequent post I will show how to make use of some more advanced features of the JSR 303 API, such as custom constraint annotations and validation groups.