Mindmajix

Hibernate Validator Tutorial

Hibernate Validator Tutorial

Input validations can happen at different places in applications. Custom and possible duplicate code can be anywhere in the applications. Not to mention they are usually part of logic in the applications. Hibernate Validator is a reference implementation of Bean Validation (http://beanvalidation.org/). Bean Validation (added as part of Java EE 6) is a framework that defines a metadata model and API for JavaBeans validation. Constraints on JavaBeans can be expressed via annotations (the default metadata model) and can be extended through XML constraint mappings. Bean Validation 1.1 allows put constraints to the parameters or return values on methods or constructors.

For Hibernate Validator, the current stable version is 5.2.2, at the time of writing. You can download it from http://hibernate.org/validator/.

APPLYING CONSTRAINTS

Field-level constraints

Let’s start with a simple example to show how to apply constraints to fields defined in a class. Applying constraints on instance fields directly is called field-level constraints. Constraints on static fields are not supported. More than one constraint can apply on the same field and constraints are combined by a logical AND.

Hibernate Validator extends Bean Validation. The built-in constraints include those defined in Bean Validation API (under package javax.validation.constraints) and those added to Hibernate Validator API (under package org.hibernate.validator.constraints). In the following example, name should not be blank (using @NotBlank constraint) and price should not be less than 0 (using @Min constraint):

import javax.validation.constraints.Min;
import org.hibernate.validator.constraints.NotBlank;
public class Book {
@NotBlank
private String name;
@Min(value=0)
private double price;
public Book() {
}
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}

Next, we can do a validation on those constraints via an instance of Validator created from a ValidatorFactory. Both ValidatorFactory and Validator are thread-safe. The validate(T object, Class<?>… groups) method in the Validator is to do validation on all constraints of an object. A set of ConstraintViolation objects is returned. If a validation succeeds, an empty set is returned. In the following example, an instance of Book that violates all constraints defined in the Book class:

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class BookExample1 {
public static void main(String[] args) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Book book1 = new Book(null, -1);
Set<ConstraintViolation<Book>> violations = validator.validate(book1);
for(ConstraintViolation<Book> violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” + violation.getMessage());
}
}
}

The following is the output:

price: must be greater than or equal to 0
name: may not be empty

Error messages

Each constraint annotation has a default error message. It can be replaced by an optional message element. For example,

@NotBlank(message=”Cannot be blank”)

An error message can contain additional information through message parameters or message expressions. A message parameter is enclosed in {}. This allows referencing to elements in the annotation. A message expression is enclosed in ${}. This allows using expressions defined in Unified Expression Language (EL), an expression language based on JSP EL. For example,

@Min(value=0, message=”Invalid value ‘${validatedValue}’. It must be greater than or equal to {value}.”)

An error message can be provided from a resource bundle too. All you need to do is to create a file, ValidationMessages.properties, and add it to the classpath. For example, {constraints.price.error} is a message parameter that is used as a key in the resource bundle:

@Min(value=0, message=”{constraints.price.error}”)

And, add an entry in the ValidationMessages.properties:

constraints.price.error=Invalid value ‘${validatedValue}’. It must be greater than or equal to {value}.

Property-level constraints

To do a validation on a property, you can use the validateProperty(T object, String propertyName, Class<?>… groups) method. For example,

validator.validateProperty(book1, “price”);

Similarly, you can apply property-level constraints by annotating getter methods on classes that follows JavaBeans standard. But, do not mix field-level constraints with property-level constraints within a class. This might cause a field to be validated more than once.

VALIDATING PARAMETERS

Validations can be performed to methods or constructors by applying constraints to parameters and return values. In the following example, constraints are added to the add method to make sure a Book object is not null and quantity is at lest one:

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class BookManager {
public void add(@NotNull Book book, @Min(value=1) int quantity) {

}
}
To do a validation on parameters, you need to get an instance of ExecutableValidator.
For methods, you can use
validateParameters(T object, Method method, Object[] parameterValues, Class<?>…
groups)
For example,
import java.lang.reflect.Method;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
public class BookExample1 {
public static void main(String[] args) throws Exception {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
BookManager manager = new BookManager();
Method method = BookManager.class.getMethod(“add”, Book.class, int.class);
Book book = new Book(“Java”, 25);
Object[] params = {book, 0};
Set<ConstraintViolation<BookManager>> violations =
executableValidator.validateParameters(
manager, method, params);
for(ConstraintViolation<BookManager> violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” + violation.getMessage());
}
}
}

The validation performed in this example is not cascaded. That means when a Book object violates any constraints, it is not going to fail on validation because no validation is performed on a Book. A cascaded validation is to validate contained objects on those annotated with @Valid. For example,
public void add(@NotNull @Valid Book book, @Min(value=1) int quantity)
To validate return values on methods, you can use
validateReturnValue(T object, Method method, Object returnValue, Class<?>… groups)
For constructors, you can use
validateConstructor(Constructor<? extends T> constructor, Object[] parameterValues,Class<?>… groups)
for parameters, and
validateConstructorReturnValue(Constructor<? extends T> constructor, T createdObject, Class<?>… groups)
for created object.

INHERITANCE

Constraints are inherited through inheritance. For example, an Item interface is declared as:

public interface Item {
@NotBlank
public String getName();
@Min(value=0)
public double getPrice();
}
Now, the Book class implements the Item interface:
public class Book implements Item {
private String name;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}

Run the following code, same validation still applies:

Item book1 = new Book(null, -1);
Set<ConstraintViolation<Item>> violations = validator.validate(book1);

For fields, any additional constraints in overriding methods will be validated on top of those defined in the super classes. For example, modify the Book class by adding a property-level constraint, such as:

@Min(value=5)
public double getPrice() {
return price;
}

Now, you will get the following output with an additional validation:

price: must be greater than or equal to 5
name: may not be empty
price: must be greater than or equal to 0

Note: Constraints are evaluated in no particular order. You might see the output in different order.

Parameter constraints can be inherited too. For example, a Manager interface is declared as:

public interface Manager {
public void add(@NotNull Item item, @Min(value=1) int quantity);
}
Now, the BookManager class implements the Manager interface, such as:
public class BookManager implements Manager {
@Override
public void add(Item item, int quantity) {

}
}

What if BookManager overrides the add() method with different constraints, such as:

public class BookManager implements Manager {
@Override
public void add(@NotNull @Valid Item item, @Min(value=1) int quantity) {

}
}

You will get the following error message during runtime because this is not allowed:

Exception in thread “main” javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not alter the parameter constraint configuration, but method public void BookManager.add(Item,int) changes the configuration of public abstract void Manager.add(Item,int)…

GROUPING CONSTRAINTS

As you can see, all the validation methods introduced earlier take a varargs parameter, Class<?>… groups, as the last parameter. When an optional groups element is not specified in a constraint, the default group, javax.validation.groups.Default, is used. In some cases, only a subset of constraints needs to be validated. This can be done through groups. Each group has to be an interface (a marker interface).

Let’s use a process of shopping cart as an example. Part of process is to ask a shopper to sign in as a member or to remain as a guest. For a member, only username and password are needed. For a guest, only address and email are needed. So, validation can be done by breaking constraints into two groups: member and guest:

public class UserInfo {
@NotBlank(groups=MemberGroup.class)
private String username;
@NotBlank(groups=MemberGroup.class)
private byte[] password;
@NotBlank(groups=GuestGroup.class)
private String address;
@NotBlank(groups=GuestGroup.class)
private String email;

}

Groups are defined as follows:

public interface MemberGroup {
}
public interface GuestGroup {
}

The following example is to run validations on the same instance of UserInfo in three scenarios: using default group, using guest group, and using member group:

public class GroupingExample1 {
private static Validator validator;
public static void main(String[] args) {
******ebook converter DEMO Watermarks*******
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
UserInfo user1 = new UserInfo(null, null);
validate(user1);
validate(user1, GuestGroup.class);
validate(user1, MemberGroup.class);
}
private static <T> void validate(T obj, Class<?>… groups) {
Set<ConstraintViolation<T>> violations = validator.validate(obj, groups);
if(!violations.isEmpty()) {
System.out.println(“Violations:”);
for(ConstraintViolation<T> violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” +
violation.getMessage());
}
} else {
System.out.println(“No violations”);
}
}
}

The following is the output:

No violations
Violations:
email: may not be empty
address: may not be empty
Violations:
username: may not be empty
password: may not be empty

PROGRAMMATIC CONSTRAINTS

Hibernate Validator also provides API for configuring constraints programmatically. This provides flexibility on changing constraints dynamically at runtime instead of annotating constraints at Java classes.

To configure constraints programmatically, you need to create a new ConstraintMapping, a top level class for constraint configuration, for constraint mapping. Then, constraints can be configured on classes.

The following example replaces the example of field-level constraints without using constraint annotations:

import java.lang.annotation.ElementType;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.cfg.defs.MinDef;
import org.hibernate.validator.cfg.defs.NotBlankDef;
public class BookExample1 {
public static void main(String[] args) {
HibernateValidatorConfiguration configuration = Validation
.byProvider(HibernateValidator.class)
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping.type(Book.class)
.property(“name”, ElementType.FIELD)
.constraint(new NotBlankDef())
.property(“price”, ElementType.FIELD)
.constraint(new MinDef().value(0));
Validator validator = configuration.addMapping(constraintMapping)
.buildValidatorFactory()
.getValidator();
Book book1 = new Book(null, -1);
Set<ConstraintViolation<Book>> violations = validator.validate(book1);
for(ConstraintViolation<Book> violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” + violation.getMessage());
}
}
}

CREATING A CUSTOM CONSTRAINT

To create a custom constraint using an annotation, an annotation type needs to be declared first. An annotation type is declared with the @Interface keyword. Several predefined Java annotation types can be included in other annotation types, such as:

@Target restricts what kind of Java elements the annotation can be applied to, such as fields, methods, or parameters. For example, ElementType.FIELD indicates the annotation can be applied to a field or property.

@Retention specifies how the annotation is retained, such as source level, compile time, or runtime. For example, RetentionPolicy.RUNTIME indicates the annotation can be used at runtime.

@Documented indicates the annotation is included in the Java doc. By default, this is not true.

@Inherited indicates the annotation type can be inherited from the super class. By default, this is not true.

All elements of an annotation are declared similar to a method. Optional default values can be provided through the default keyword.

The following example is to add a new constraint annotation that can validate the code field in the Book class:

public class Book {
@NotBlank
private String name;
@Min(value=0)
private double price;
@NotBlank
@CodeConstraint(prefixList={“A-“, “B-“})
private String code;

}

Creating a constraint annotation

To make an annotation type as a constraint annotation, you need to use @Constraint. Also, any constraint annotation needs to provide three required elements: message, groups, and payload. @CodeConstraint has an additional element, prefixList.

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)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy=CodeConstraintValidator.class)
public @interface CodeConstraint {
String message() default “Invalid code”;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] prefixList();
}

The purpose of message element is to provide an error message when a validation fails. Here, a default message is provided. An error message can be provided from a resource bundle too. All you need to do is to create a file, ValidationMessages.properties, and add it to the classpath. For example,

String message() default “{constraints.code.error}”;

And, add an entry in the ValidationMessages.properties:

constraints.code.error=Invalid code

Creating an validator

A constraint validator needs to implement the following interface:

ConstraintValidator<A extends Annotation, T>

The first parameter is the constraint annotation. The second parameter is the data type of object to be validated. ConstraintValidator defines two methods:

void initialize(A annotation): This method is called to initialize the validator before the isValid method is called.

boolean isValid(T value, ConstraintValidatorContext context): This contains the actual logic of validation. This method can be accessed concurrently. You need to make sure it is thread-safe.

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CodeConstraintValidator implements
ConstraintValidator<CodeConstraint, String> {
private String[] prefixList;
@Override
public void initialize(CodeConstraint annotation) {
this.prefixList = annotation.prefixList();
}
@Override
public boolean isValid(String object, ConstraintValidatorContext context) {
if(object == null) {
return false;
}
boolean flag = false;
for(String prefix : prefixList) {
if(object.startsWith(prefix)) {
flag = true;
break;
}
}
return flag;
}
}


0 Responses on Hibernate Validator Tutorial"

Leave a Message

Your email address will not be published. Required fields are marked *

Copy Rights Reserved © Mindmajix.com All rights reserved. Disclaimer.
Course Adviser

Fill your details, course adviser will reach you.