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 ..

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

	}
}
jsr303 unit tests 222x300
Figure 1. Test results in the JUnit Runner

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

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