You 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 ..
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
Tutorial Sources
You may download the source for these example from GitHub or check it out like this
git clone http://github.com/hascode/bean-validation-tutorial.git