Java Bean Validation using Hibernate Validator

Learn to use hibernate validator to validate the field values in a Java bean. Bean validation API offers some very useful annotations that can be applied to any bean property for the purpose of maintaining data integrity.

Bean validation in Java is supported via JSR-303 (Bean Validation 1.0), JSR-349 (Bean Validation 1.1) and JSR 380 (Bean Validation 2.0).

1. Dependencies

Start with adding the latest version of hibernate-validator module. This transitively pulls in the dependency to the Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api).

Older versions had the dependency on javax.validation:validation-api.

If we want to use javax.validation.ValidationFactory in Hibernate 6 or later versions, we can specify the jakarta.persistence.validation.factory property and set its value to javax.validation.ValidationFactory.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.4.Final</version>
</dependency>

Bean validation allows expressions inside the error messages. To parse these expressions, we must add a dependency on both the expression language API and an implementation of that API. The latest version of the validator requires an implementation of Jakarta Expression Language.

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.1</version>
</dependency>

We can additionally include Hibernate Validator Annotation Processor helps in preventing mistakes by plugging into the build process and raising compilation errors whenever constraint annotations are incorrectly used.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>7.0.4.Final</version>
</dependency>

Finally, if we are not running the application inside a Jakarta EE application server then we should add hibernate-validator-cdi dependency as well. This provides CDI managed beans for Validator and ValidatorFactory and enables dependency injection in constraint validators as well as custom message interpolators, traversable resolvers, constraint validator factories, parameter name providers, clock providers and value extractors.

2. Getting Started with Bean Validation

Let’s quickly run a demo program to have a basic understanding before deep diving into details.

2.1. Annotate Model with JSR-380 Annotations

Start with applying the validation constraints in the fields of a model class. We are using the User class and applied constraints to id, name and email fields.

package com.howtodoinjava.example.model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {

    @NotNull(message = "Please enter id")
    private Long id;

    @Size(max = 20, min = 3, message = "{user.name.invalid}")
    @NotEmpty(message = "Please enter name")
    private String name;

    @Email(message = "{user.email.invalid}")
    @NotEmpty(message = "Please enter email")
    private String email;

    //Setters and Getters
}

2.2. Default Resource Bundle

By default, all messages are resolved from ValidationMessages.properties file in the classpath. If the file does not exist, the message resolution does not happen.

user.name.invalid=Invalid Username
user.email.invalid=Invalid Email

2.3. Executing the Validations

Now let’s execute the bean validation on User instance.

public class TestHibernateValidator 
{
    public static void main(String[] args) 
    {
        //Create ValidatorFactory which returns validator
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
         
        //It validates bean instances
        Validator validator = factory.getValidator();
 
        User user = new User(null, "1", "abcgmail.com");
 
        //Validate bean
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
 
        //Show errors
        if (constraintViolations.size() > 0) {
            for (ConstraintViolation<User> violation : constraintViolations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("Valid Object");
        }
    }
}

Program output:

Please enter id
Invalid Email
Invalid Username

3. Applying Constraints

Annotation constraints can be applied over four places in a class:

  • field constraints
  • property constraints
  • container element constraints
  • class constraints

Obviously, not all constraints can be placed on all of these levels. 

//Class level constraint
@ValidUserDemograpics
public class User {
   
   //Field level constraint
   @NotNull
   private String name;

   //Property level constraint
   @NotNull
   public String getEmail() {
      return email;
   }

   //other fields and accessors
}

4. Bootstrapping ValidationFactory

We can obtain a Validator by retrieving a ValidatorFactory via one of the static methods on jakarta.validation.Validation and calling getValidator() on the factory instance.

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

In case there are multiple validation providers in the runtime, we can get a specific validator by its name.

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

If the application supports CDI, it is very easy to retrieve ValidatorFactory and Validator instances with @jakarta.inject.Inject. In case, the application is running inside a Jakarta EE server, we can use @Resource annotation as well.

@Inject
private ValidatorFactory validatorFactory;

@Inject
private Validator validator;

In case of multiple providers, use @HibernateValidator to configure the specific validator.

@Inject
@HibernateValidator
private ValidatorFactory validatorFactory;

@Inject
@HibernateValidator
private Validator validator;

5. Custom Resource Bundles

By default, framework picks up validation messages from ValidationMessages.properties file in classpath. We may configure the custom property files as below.

For example, put these two property files in classpath:

  • messages.properties
  • otherMessages.properties

Add both property files to ResourceBundleMessageInterpolator.

Validator validator = Validation.byDefaultProvider()
        .configure()
        .messageInterpolator(
                new ResourceBundleMessageInterpolator(
                        new AggregateResourceBundleLocator(
                                Arrays.asList(
                                        "messages",
                                        "otherMessages"
                                )
                        )
                )
        )
        .buildValidatorFactory()
        .getValidator();

6. Runtime Message Interpolation

Message interpolation is the process of creating error messages for violated Bean Validation constraints.

6.1. Parameters Resolution

During message resolution, we can use runtime values to make validation messages more meaningful. This parameter value resolution in messages happens in two ways:

  1. To resolve values in annotation attributes, simply enclose them with curly braces. E.g. {min} or {max}.
  2. To resolve the field’s runtime value, use placeholder ${validatedValue}.

6.2. Demo

Start with creating message resource file with placeholders.

user.name.invalid='${validatedValue}' is an invalid name. It must be minimum {min} chars and maximum {max} chars.

Now annotate the field in the Bean class.

@Size(max = 20, min = 3, message = "{user.name.invalid}")
private String name;

Now run the validator and observe the output.

User user = new User(23l, "xy", "abc@gmail.com");
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
'xy' is an invalid name. It must be minimum 3 chars and maximum 20 chars.

7. Bean Validation Annotations List

Now when we know how to use hibernate validator in a programmatic manner. Let’s go through all the annotations which we can use in bean classes.

7.1. Default Annotations

AnnotationDescription
@AssertFalseChecks that the annotated element is false
@AssertTrueChecks that the annotated element is true
@DecimalMax(value=, inclusive=)Checks whether the annotated value is less than the specified maximum BigDecimal value, when inclusive=false. Otherwise, whether the value is less than or equal to the specified maximum.
@DecimalMin(value=, inclusive=)Checks whether the annotated value is larger than the specified minimum BigDecimal value.
@Digits(integer=, fraction=)Checks whether the annotated value is a number having up to integer digits and fraction fractional digits.
@EmailChecks whether the specified character sequence is a valid email address.
@Max(value=)Checks whether the annotated value is less than or equal to the specified maximum.
@Min(value=)Checks whether the annotated value is higher than or equal to the specified minimum
@NotBlankChecks that the annotated character sequence is not null and the trimmed length is greater than 0.
@NotEmptyChecks whether the annotated element is not null nor empty.
@NullChecks that the annotated value is null
@NotNullChecks that the annotated value is not null
@Pattern(regex=, flags=)Checks if the annotated string matches the regular expression regex considering the given flag match
@Size(min=, max=)Checks if the annotated element’s size is between min and max (inclusive)
@PositiveChecks if the element is strictly positive. Zero values are considered invalid.
@PositiveOrZeroChecks if the element is positive or zero.
@NegativeChecks if the element is strictly negative. Zero values are considered invalid.
@NegativeOrZeroChecks if the element is negative or zero.
@FutureChecks whether the annotated date is in the future.
@FutureOrPresentChecks whether the annotated date is in the present or in the future.
@PastChecks whether the annotated date is in the past
@PastOrPresentChecks whether the annotated date is in the past or in the present.

7.2. Hibernate Validator Specific Annotations

In addition to the constraints defined by the Bean Validation API, Hibernate Validator provides several useful custom constraints which are listed below.

AnnotationDescription
@CreditCardNumber( ignoreNonDigitCharacters=)Checks that the annotated character sequence passes the Luhn checksum test. Note, this validation aims to check for user mistakes, not credit card validity!
@Currency(value=)Checks that the currency unit of the annotated javax.money.MonetaryAmount is part of the specified currency units.
@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)Checks that annotated java.time.Duration element is not greater than the specified value in the annotation.
@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)Checks that annotated java.time.Duration element is not less than the specified value in the annotation.
@EANChecks that the annotated character sequence is a valid EAN barcode. The default is EAN-13.
@ISBNChecks that the annotated character sequence is a valid ISBN.
@Length(min=, max=)Validates that the annotated character sequence is between min and max included.
@Range(min=, max=)Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
@UniqueElementsChecks that the annotated collection only contains unique elements.
@URLChecks if the annotated character sequence is a valid URL according to RFC2396.
@CodePointLength(min=, max=, normalizationStrategy=)Validates that code point length of the annotated character sequence is between min and max included.
@LuhnCheck(startIndex= , endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)Checks that the digits within the annotated character sequence pass the Luhn checksum algorithm.
@Normalized(form=)Validates that the annotated character sequence is normalized according to the given form.
@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)Checks that the digits within the annotated character sequence pass the generic mod 10 checksum algorithm.
@Mod11CheckChecks that the digits within the annotated character sequence pass the mod 11 checksum algorithm.
@ScriptAssert(lang=, script=, alias=, reportOn=)Checks whether the given script can successfully be evaluated against the annotated element. A JSR 223 implementation must be present in the classpath.

8. Conclusion

In this hibernate tutorial, we learned to apply the bean validation constraints in a simple POJO class. We also learned to inject the bean Validator interface and then validate the POJO against the applied constraints.

We also learned to customize the resource bundle and message interpolation. Finally, we went through the complete list of bean validation constraints provided by Jakarta persistence API and Hibernate provided custom constraints.

Happy Learning !!

Leave a Reply

1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.