Bean Validation with JSR-303 and Hibernate Validator
December 14th, 2010 by Micha KopsYou want to add some validation logic to your Java beans? You want to achieve this with some shiny extendable annotations? Then give the Java Bean Validation standard aka JSR-303 a try..
We’re going to use the reference implementation for bean validation, Hibernate Validator in this tutorial but there are also links to other alternatives like Oval or Apache Bean Validation.
So let’s begin and validate some stuff ..
Contents
Prerequisites
Create a new Maven project
- Create a new Maven project using your favourite IDE with the Maven plugin or the console
mvn archetype:generate
- We have some dependencies for the Bean Validation API and we need a concrete implementation – in this case it is Hibernate Validator. Add the following dependencies to your pom.xml
<dependencies> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.0.2.GA</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> </dependencies>
- Now we need some extra Maven repositories – add the following repositories to the pom.xml
<repositories> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>JBoss repository</id> <url>http://repository.jboss.com/maven2/</url> </repository> </repositories>
- Finally we want a Java 6 compatible project so force the Maven Compiler Plugin to use the corresponding source and target level
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build>
- At last my complete pom.xml looks like this one
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hascode.tutorial</groupId> <artifactId>bean-validation-examples</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.0.2.GA</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>JBoss repository</id> <url>http://repository.jboss.com/maven2/</url> </repository> </repositories> </project>
Create a Bean to validate
Now we need a class to be validated, for our example there will be a class User. A user has a first and a lastname which must not be null and must be at least two characters long. Also our user has a birthday date that must be a past date and the user has a special customer number in a specified format.
- Create a new class named UserBean in the package com.hascode.tutorial.bean add setters, getters and the constraints for our validation
package com.hascode.tutorial.bean; import java.util.Date; import javax.validation.constraints.NotNull; import javax.validation.constraints.Past; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; public class UserBean { @NotNull @Size(min = 2) private String firstName; @NotNull @Size(min = 2) private String lastName; @NotNull @Past private Date birthday; @NotNull @Pattern(regexp = "^[a-zA-Z]{2}-\\d+$") private String customerNumber; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getCustomerNumber() { return customerNumber; } public void setCustomerNumber(String customerNumber) { this.customerNumber = customerNumber; } }
JSR-303 Validation Constraints
The following constraints are available in the JSR-303 API
- AssertFalse: The element must be false
- AssertTrue: The element must be true
- DecimalMax: The element must be a number and lower or equal to the specified value
- DecimalMin: The element must be a number and greater or equal to the specified value
- Digits: Must be a number within an accepted range
- Future: Must be a date in the future
- Max: Must be a number with a value lower or equal to the specified maximum
- Min: Must be a number with a value greater or equal to the specified minimum
- NotNull: Must not be null
- Null: Must be null
- Past: Must be a past date
- Pattern: Must match a defined regular expression
- Size: Must match defined boundaries. Valid for strings, collections, maps and arrays
Write a unit test
Now we want to see some results .. create a class named UserBeanTest in src/test/java
package com.hascode.tutorial.bean; import java.util.Date; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import junit.framework.Assert; import org.junit.BeforeClass; import org.junit.Test; public class UserBeanTest { private static Validator validator; @BeforeClass public static void setUp() { validator = Validation.buildDefaultValidatorFactory().getValidator(); } @Test public void testCreateUser() { UserBean user = new UserBean(); Set<ConstraintViolation<UserBean>> violations = validator.validate(user); Assert.assertEquals(4, violations.size()); // 4x // "must not be null error" user.setFirstName("Howard Phillips"); user.setLastName("Lovecraft"); user.setBirthday(new Date(600000000)); user.setCustomerNumber("XXX"); violations = validator.validate(user); /** * birthday is not a past date and customer number does not match the * specified format */ Assert.assertTrue("there are still some errors", (violations.size() > 0)); user.setCustomerNumber("EN-123456"); violations = validator.validate(user); Assert.assertEquals(0, violations.size()); // finally everything // validates } }
Define custom constraints
In the following step we want to define a custom constraint that replaces the @Pattern constraint for the customer number field.
Creating custom constraints is quite easy .. just create a new annotation, specify the parameters and create an implementation of ConstraintValidator for the new constraint .. here we go …
- Create a new annotation named CustomerNumber in the package com.hascode.tutorial.constraint
package com.hascode.tutorial.constraint; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; @Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = CustomerNumberValidator.class) public @interface CustomerNumber { String message() default "The given customer number is invalid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
- Create a new class in the same package named CustomerNumberValidator that implements ConstraintValidator
package com.hascode.tutorial.constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CustomerNumberValidator implements ConstraintValidator<CustomerNumber, String> { @Override public void initialize(CustomerNumber number) { } @Override public boolean isValid(String customerNumber, ConstraintValidatorContext ctx) { if (customerNumber == null) return false; if (customerNumber.matches("^[a-zA-Z]{2}-\\d+$")) return true; return false; } }
- Change the @Pattern annotation in the UserBean to the new constraint @CustomerNumber
@NotNull @CustomerNumber private String customerNumber;
- Change the unit test to expect five constraint violations ;)
Assert.assertEquals(5, violations.size());
- The tests should run without errors
Other Implementations
- OVal – The Object Validation Framework for Java
- Apache Bean Validation Incubator Project
- Spring 3 Validation
Tutorial Sources
You may download the source for these example from Bitbucket.org or check it out like this
hg clone http://bitbucket.org/hascode/bean-validation-tutorial
Resources
- Java Community Process: JSR-303 – Bean Validation
- Hibernate Validator Project Site
- Gunnar Morling: Getting started with JSR 303 (Bean Validation)
- Nicholas Fränkel: Bean validation and JSR 303
- Apache Bean Validation
- OVal Validation Framework
- Spring 3 Bean Validation
Tags: bean validation, example, hibernate validator, java persistence api, jpa, jsr-303, oval, tutorial, validation
April 14th, 2011 at 7:12 am
How can I link up to my custom constraints, if I separate the validator and the XML file by using Oval?
February 24th, 2012 at 12:28 pm
Hi,
Congratz for your post.
Just a little thought about it.. I’m not sure if Spring 3 bean validation is a JSR-303 real implementation. It looks like that it just provide an integration point with a BeanValidator provider as the Spring documentation says:
“A JSR-303 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically.”
Regards,
LeoLuz