Hibernate Validator : Getting Started

What is Hibernate Validator? Any advantages?
Are there any new features in Java 8?
How to define Constraints for a Model?
How to create a Customized Constraint?
How to validate the Constraints and get the Violations?

Star_coin-30 This tutorial’s purpose is to introduce  Hibernate Validator in simple words and to help you setup quickly an example. If you want to strengthen your knowledge you can’t find better than the Official Documentation.

code-source This tutorial’s code source is in letsprog Github.


question_80px What is Hibernate Validator?

Hibernate Validator is an implementation of Java eXtensions Validation API (Java built-in). Its purpose is to declare and to validate constraints (over a bean, an object fields, a method arguments) using built-in or customized annotations, without messing with your code. Exemple:

public class Car {
	
	@NotEmpty // An empty string "" will be considered as a constraint violation.
	private String brand;
	
	public Car(){}
	
	public Car(String brand){
		this.brand = brand;
	}

}

moneda_de_oro Hibernate Validator advantages

  • You can validate your object when you want to. For example, Car unbrandedCar = new Car(“”) won’t show any problem until you decide to validate the object : validator.validate(unbrandedCar).
  • Hibernate Validator works as your reporter, and not as a decider in your place. After validating the instance, Hibernate Validator will report to you the non-respected rules. And it’s up to you to judge the instance as rejected or not.
  • Hibernate Validator let you customize the rules and so you can create your own annotation representing the rule.
  • Hibernate Validator let you categorize the rules into errors/warnings/information/… So, it’s not always about errors 🙂
  • The best of is that Hibernate Validator won’t throw to you an exception if a rule is not respected, but it will rather construct a Set of Violations.

Thank you JBoss family, you have given us such another cute baby…!

hibernate validator


reqs Tutorial Requirements

Requirements :

question_80px Do we really need to have Java 8 for this tutorial?

Yes. Java 8 has added in its Javax Validation API support for customized constraints for Generic Types (parameterized types). Example :

List<@MyCustomConstraint String> someListOfConstrainedStrings;

Maven Dependencies

Add the following dependencies to your pom.xml file:

<dependencies>

	<!-- Java eXtensions for Validation API -->
	<dependency>
		<groupId>javax.validation</groupId>
		<artifactId>validation-api</artifactId>
		<version>1.1.0.Final</version>
	</dependency>
	
	<!-- Java eXtensions for Unified Expression Language API -->
	<dependency>
		<groupId>javax.el</groupId>
		<artifactId>javax.el-api</artifactId>
		<version>2.2.4</version>
	</dependency>
	
	<!-- Hibernate Validator as implementation of the JSR 349 Bean Validation Specification -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-validator</artifactId>
		<version>5.3.0.Final</version>
	</dependency>
	
</dependencies>

Since we want to make a Java 8 compliant project, configure Maven Compiler Plugin as follows :

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.3</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
	</plugins>
</build>

Remark : In case you didn’t include javax.el-api dependency, you would get this expection when trying to get an instance of the constraints validator :

Exception in thread "main" javax.validation.ValidationException: HV000183: Unable to load 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead

Constrained Model

The Car class :

package com.letsprog.hbvalidator.learning.model;

import org.hibernate.validator.constraints.NotEmpty;

public class Car {
	
	@NotEmpty
	private String brand;
	
	public Car(){}
	
	public Car(String brand){
		this.brand = brand;
	}

	// getters and setters

}
  • The brand field is annotated by @NotEmpty, that means brand cannot be null nor an empty string “”. However, it can be filled by ”   ” (3 spaces).

The Person class :

package com.letsprog.hbvalidator.learning.model;

import java.util.List;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.NotBlank;

import com.letsprog.hbvalidator.learning.validator.PositiveNumberConstraint;
import com.letsprog.hbvalidator.learning.validator.Severity;

public class Person {
	
	@NotBlank
	private String name;
	
	@NotBlank
	@Pattern(regexp="^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+).([a-zA-Z]{2,3})$")
	private String emailAddress;
	
	@NotNull
	@PositiveNumberConstraint(payload=Severity.Error.class) // Customized Constraint Annotation
	private String age;
	
	@Valid
	private List<@PositiveNumberConstraint String> luckyNumbers;
	
	@NotNull
	@Valid 
	private Car car;

	// getters and setters
	
}
  • The name field is annotated by @NotBlank, so it cannot be null nor an empty string “”, nor spaces! And so after trimming the value, its size should be > 0.
  • The emailAddress field, besides that it is @NotBlank, it is annotated @Pattern in which we have defined the regular expression which an email address should respect.
  • The age field has multiple constraints. It should not be null (@NotNull), and has a customized constraint @PositiveNumberConstraint which imposes the age to be a positive number. Moreover, we have defined a customized category of the Constraint Violation : payload=Severity.Error.class. We will discover in the next section how to define customized constraint with customized severity.
  • The luckyNumbers field has a parameterized type, that is a List of String. As we are using Java 8, we can add a rule over a type parameters. Adding the customized constraint @PositiveNumberConstraint, a lucky number shouldn’t be a negative one 🙂
  • The car field shouldn’t be null (@NotNull). Being annotated by @Valid, this field will be validated in cascade. And so any constrainted sub-field will be validated, too. In our case, it is the subfield car.brand if not empty.

Customized Validator

Defining the Customized Constraint Annotation

The PositiveNumberConstraint customized annotation :

package com.letsprog.hbvalidator.learning.validator;

import java.lang.annotation.Documented;
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.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PositiveNumberConstraintValidator.class)
@Documented
public @interface PositiveNumberConstraint {
	
	String message() default "This field must be positive";
	
	Class<?>[] groups() default {};
	
	Class<? extends Payload>[] payload() default {};

}
  • @Target : To specify the kind of “things” we want to make validatable. I have only mentionned the necessary ones :
    • ElementType.FIELD : To make a field validatable.
    • ElementType.TYPE_USE : To make a Type parameter validatable, such . If this annotation is missing, you will get the compilation error The annotation @PositiveNumberConstraint is disallowed for this location.

hibernate_validator_type_use_necessary

  • @Constraint : To specify the implementation which will override the predicate of verifying the validity of the thing.

Defining the Customized Constraint logic

The PositiveNumberConstraintValidator class  :

package com.letsprog.hbvalidator.learning.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PositiveNumberConstraintValidator implements ConstraintValidator<PositiveNumberConstraint,String>{

	@Override
	public void initialize(PositiveNumberConstraint constraintAnnotation) {
	}

	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		
		boolean isPostive = false;
		
		if(Integer.parseInt(value) >= 0){
			isPostive = true;
		}
		
		return isPostive;
	}

}

This class must implement the Generic Interface ConstraintValidator. This interface must have as the first parameter the Constraint Annotation (PositiveNumberConstraint), and as the second parameter the Constrained Data Type (String).

The validation logic goes into the overriden method isValid(..).

Defining the Customized Constraint Severity

package com.letsprog.hbvalidator.learning.validator;

import javax.validation.Payload;

public class Severity {

	public interface Info extends Payload {

	}

	public interface Warning extends Payload {

	}

	public interface Error extends Payload {

	}

}

 


Validation

close-the-end-super-mario-s

Using the Default Java eXtention Validation API implementation

Let’s create 2 persons, one with valid data and the other one no :

package com.letsprog.hbvalidator.learning;

import java.util.Arrays;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import com.letsprog.hbvalidator.learning.model.Car;
import com.letsprog.hbvalidator.learning.model.Person;
import com.letsprog.hbvalidator.learning.validator.Severity;

public class DefaultProviderMain {

	public static void main(String[] args) {

		Person validPerson = new Person();

		validPerson.setName("Farah");
		validPerson.setAge("26");
		validPerson.setEmailAddress("farah.fertassi@letsprog.com");
		validPerson.setLuckyNumbers(Arrays.asList("0","2"));
		validPerson.setCar(new Car("BMW"));

		Person inValidPerson = new Person();

		inValidPerson.setName("Koko");
		inValidPerson.setAge("-18"); // Constraint Violation 1 : Negative Number.
		inValidPerson.setEmailAddress("farah.fertassi"); // Constraint Violation 2 : Email address pattern not respected.
		inValidPerson.setLuckyNumbers(Arrays.asList("3","-4","45")); // Constraint Violation 2 : One of the List's strings is a negative number.
		inValidPerson.setCar(new Car("")); // Constraint Violation 3 : The constructed will initialize car.brand by an empty string.

		// In our pom.xml only Hibernate Validator is included. This makes it the default validator.
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();

		// Being a valid person, this Set should be empty.
		Set<ConstraintViolation<Person>> validPersonConstraintViolations = validator.validate(validPerson);
		System.out.println("validPersonConstraintViolations.size : "+validPersonConstraintViolations.size());

		// This set should have 4 Constraint Violations.
		Set<ConstraintViolation<Person>> inValidPersonConstraintViolations = validator.validate(inValidPerson);
		
		
		for (ConstraintViolation<Person> report : inValidPersonConstraintViolations){

			System.out.println("report.getConstraintDescriptor().getPayload().size() : "+report.getConstraintDescriptor().getPayload().size());

			Boolean isError = false;
			if(report.getConstraintDescriptor().getPayload().iterator().hasNext())
				isError = Severity.Error.class.equals(report.getConstraintDescriptor().getPayload().iterator().next());
			System.out.println("isError : "+isError);
		}

		System.out.println("inValidPersonConstraintViolations.size : "+inValidPersonConstraintViolations.size());
		System.out.println("Constraints Violations : "+inValidPersonConstraintViolations.toString());

	}

}
  • From line 30 to 33 : Assigning bad values to the second person, causing constraints violation.
  • Line 36 : As we are including in our project only Hibernate Validator as the Java eXtention Validation API implementation, we are sure that Hibernate Validator will be taken as the default implementation to construct the Validator Factory.
  • Line 37 : Getting the Validator instance using the created factory.
  • Line 40 : Here we do finally validate one of the persons instance, giving as a return a Set of Constraint Violations.
  • Line 53 : Checking the Constrained Violation Severity.

Specifying the Java eXtention Validation API implementation

You only need to specify the Validation Provider as following to constrauct the Validator Factory :

	// If multiple Validation Providers exist, specify the one to use
	HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
	ValidatorFactory validatorFactory = validatorConfiguration.buildValidatorFactory();
	Validator validator = validatorFactory.getValidator();

Testing

super_mario_end

Run your main class and check the log :

oct. 19, 2016 10:59:33 PM org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 5.3.0.Final
validPersonConstraintViolations.size : 0
report.getConstraintDescriptor().getPayload().size() : 0
isError : false
report.getConstraintDescriptor().getPayload().size() : 0
isError : false
report.getConstraintDescriptor().getPayload().size() : 0
isError : false
report.getConstraintDescriptor().getPayload().size() : 1
isError : true
inValidPersonConstraintViolations.size : 4
Constraints Violations : [ConstraintViolationImpl{interpolatedMessage='doit respecter "^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+).([a-zA-Z]{2,3})$"', propertyPath=emailAddress, rootBeanClass=class com.letsprog.hbvalidator.learning.model.Person, messageTemplate='{javax.validation.constraints.Pattern.message}'}, ConstraintViolationImpl{interpolatedMessage='This field must be positive', propertyPath=luckyNumbers[1].<collection element>, rootBeanClass=class com.letsprog.hbvalidator.learning.model.Person, messageTemplate='This field must be positive'}, ConstraintViolationImpl{interpolatedMessage='ne peut pas être vide', propertyPath=car.brand, rootBeanClass=class com.letsprog.hbvalidator.learning.model.Person, messageTemplate='{org.hibernate.validator.constraints.NotEmpty.message}'}, ConstraintViolationImpl{interpolatedMessage='This field must be positive', propertyPath=age, rootBeanClass=class com.letsprog.hbvalidator.learning.model.Person, messageTemplate='This field must be positive'}]