Hibernate Validator Tutorial

Rating: 4.3
  
 
6378
  1. Share:
Hibernate Validator Quiz

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 putting 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, the name should not be blank (using @NotBlank constraint) and the 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;
}
}

 MindMajix Youtube Channel


Next, we can do validation on those constraints via an instance of a 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 validation succeeds, an empty set is returned. In the following example, an instance of a 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> violations = validator.validate(book1);
for(ConstraintViolation 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 validation on a property, you can use the validate property(T object, String propertyName, Class… groups) method. For example,
validator.validate property(book1, “price”);
Similarly, you can apply property-level constraints by annotating getter methods on classes that follow the 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 least 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 validation on parameters, you need to get an instance of ExecutableValidator.
For methods, you can use
validate parameters(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> violations =
executableValidator.validateParameters(
manager, method, params);
for(ConstraintViolation 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
validated constructor(Constructor constructor, Object[] parameterValues,Class… groups)
for parameters, and
validateConstructorReturnValue(Constructor constructor, T created object, Class… groups)
for the 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> 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 a 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” java. 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 the process of the shopping cart as an example. Part of the process is to ask a shopper to sign in as a member or to remain as a guest. For a member, only a 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 void validate(T obj, Class… groups) {
Set> violations = validator.validate(obj, groups);
if(!violations.isEmpty()) {
System.out.println(“Violations:”);
for(ConstraintViolation 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:

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 void validate(T obj, Class… groups) {
Set> violations = validator.validate(obj, groups);
if(!violations.isEmpty()) {
System.out.println(“Violations:”);
for(ConstraintViolation violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” +
violation.getMessage());
}
} else {
System.out.println(“No violations”);
}
}
}

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 superclass. By default, this is not true.
All elements of 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, prefix-list.

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 void validate(T obj, Class… groups) {
Set> violations = validator.validate(obj, groups);
if(!violations.isEmpty()) {
System.out.println(“Violations:”);
for(ConstraintViolation violation : violations) {
System.out.println(violation.getPropertyPath() + “: ” +
violation.getMessage());
}
} else {
System.out.println(“No violations”);
}
}
}


The purpose of the message element is to provide an error message when 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 a validator

A constraint validator needs to implement the following interface:
ConstraintValidator
The first parameter is 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 {
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;
}
}
Join our newsletter
inbox

Stay updated with our newsletter, packed with Tutorials, Interview Questions, How-to's, Tips & Tricks, Latest Trends & Updates, and more ➤ Straight to your inbox!

Course Schedule
NameDates
Hibernate TrainingMay 07 to May 22View Details
Hibernate TrainingMay 11 to May 26View Details
Hibernate TrainingMay 14 to May 29View Details
Hibernate TrainingMay 18 to Jun 02View Details
Last updated: 08 Jan 2024
About Author

Ravindra Savaram is a Technical Lead at Mindmajix.com. His passion lies in writing articles on the most popular IT platforms including Machine learning, DevOps, Data Science, Artificial Intelligence, RPA, Deep Learning, and so on. You can stay up to date on all these technologies by following him on LinkedIn and Twitter.

read more