ValidatorFactory andValidatorValidatorFactoryMessageInterpolatorTraversableResolverConstraintValidatorFactoryParameterNameProviderClockProvider and temporal validation toleranceValueExtractorsScriptEvaluatorFactoryConstraintViolationParameterMessageInterpolatorResourceBundleLocatorParameterNameProviderValidating data is a common task that occurs throughout all application layers, from thepresentation to the persistence layer. Often the same validation logic is implemented in each layerwhich is time consuming and error-prone. To avoid duplication of these validations, developers oftenbundle validation logic directly into the domain model, cluttering domain classes with validationcode which is really metadata about the class itself.

Jakarta Bean Validation 2.0 - defines a metadata model and API for entity and method validation.The default metadata source are annotations, with the ability to override and extend the meta-datathrough the use of XML. The API is not tied to a specific application tier nor programming model. Itis specifically not tied to either web or persistence tier, and is available for both server-sideapplication programming, as well as rich client Swing application developers.

Hibernate Validator is the reference implementation of Jakarta Bean Validation. The implementation itself aswell as the Jakarta Bean Validation API and TCK are all provided and distributed under theApache Software License 2.0.
Hibernate Validator 6 and Jakarta Bean Validation 2.0 require Java 8 or later.
This chapter will show you how to get started with Hibernate Validator, the reference implementation (RI) of Jakarta Bean Validation. For the following quick-start you need:
A JDK 8
An Internet connection (Maven has to download all required libraries)
In order to use Hibernate Validator within a Maven project, simply add the following dependency toyourpom.xml:
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.5.Final</version></dependency>This transitively pulls in the dependency to the Jakarta Bean Validation API(jakarta.validation:jakarta.validation-api:2.0.2).
Hibernate Validator requires an implementation ofJakarta Expression Languagefor evaluating dynamic expressions in constraintviolation messages (seeSection 4.1, “Default message interpolation”). When your application runs in a Java EEcontainer such as JBoss AS, an EL implementation is already provided by the container. In a Java SEenvironment, however, you have to add an implementation as dependency to your POM file. For instanceyou can add the following dependency to use the Jakarta ELreferenceimplementation:
<dependency> <groupId>org.glassfish</groupId> <artifactId>jakarta.el</artifactId> <version>3.0.3</version></dependency>For environments where one cannot provide a EL implementation Hibernate Validator is offering aSection 12.10, “ |
Jakarta Bean Validation defines integration points with CDI(Contexts and Dependency Injection for Jakarta EE).If your application runs in anenvironment which does not provide this integration out of the box, you may use the HibernateValidator CDI portable extension by adding the following Maven dependency to your POM:
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator-cdi</artifactId> <version>6.2.5.Final</version></dependency>Note that adding this dependency is usually not required for applications running on a Java EEapplication server. You can learn more about the integration of Jakarta Bean Validation and CDI inSection 11.3, “CDI”.
Hibernate Validator supports running with asecurity manager being enabled.To do so, you must assign several permissions to the code bases of Hibernate Validator, the Jakarta Bean Validation API, Classmate and JBoss Logging and also to the code base calling Jakarta Bean Validation.The following shows how to do this via apolicy file as processed by the Java default policy implementation:
grant codeBase "file:path/to/hibernate-validator-6.2.5.Final.jar" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "setContextClassLoader"; permission org.hibernate.validator.HibernateValidatorPermission "accessPrivateMembers"; // Only needed when working with XML descriptors (validation.xml or XML constraint mappings) permission java.util.PropertyPermission "mapAnyUriToUri", "read";};grant codeBase "file:path/to/jakarta.validation-api-2.0.2.jar" { permission java.io.FilePermission "path/to/hibernate-validator-6.2.5.Final.jar", "read";};grant codeBase "file:path/to/jboss-logging-3.4.1.Final.jar" { permission java.util.PropertyPermission "org.jboss.logging.provider", "read"; permission java.util.PropertyPermission "org.jboss.logging.locale", "read";};grant codeBase "file:path/to/classmate-1.5.1.jar" { permission java.lang.RuntimePermission "accessDeclaredMembers";};grant codeBase "file:path/to/validation-caller-x.y.z.jar" { permission org.hibernate.validator.HibernateValidatorPermission "accessPrivateMembers";};TheWildFly application server contains Hibernate Validator out of the box.In order to update the server modules for Jakarta Bean Validation API and Hibernate Validator to the latest and greatest, the patch mechanism of WildFly can be used.
You can download the patch file fromSourceForge or from Maven Central using the following dependency:
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator-modules</artifactId> <version>6.2.5.Final</version> <classifier>wildfly-22.0.0.Final-patch</classifier> <type>zip</type></dependency>We also provide a patch for WildFly :
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator-modules</artifactId> <version>6.2.5.Final</version> <classifier>wildfly--patch</classifier> <type>zip</type></dependency>Having downloaded the patch file, you can apply it to WildFly by running this command:
$JBOSS_HOME/bin/jboss-cli.sh patch apply hibernate-validator-modules-6.2.5.Final-wildfly-22.0.0.Final-patch.zipIn case you want to undo the patch and go back to the version of Hibernate Validator originally coming with the server, run the following command:
$JBOSS_HOME/bin/jboss-cli.sh patch rollback --reset-configuration=trueAs of Hibernate Validator 6.2.5.Final, support for Java 9 and the Java Platform Module System (JPMS) is experimental.There are no JPMS module descriptors provided yet, but Hibernate Validator is usable as automatic modules.
These are the module names as declared using theAutomatic-Module-Name header:
Jakarta Bean Validation API:java.validation
Hibernate Validator core:org.hibernate.validator
Hibernate Validator CDI extension:org.hibernate.validator.cdi
Hibernate Validator test utilities:org.hibernate.validator.testutils
Hibernate Validator annotation processor:org.hibernate.validator.annotationprocessor
These module names are preliminary and may be changed when providing real module descriptors in a future release.
When using Hibernate Validator with CDI, be careful to not enable the Instead, add the full Jakarta Annotations API to the unnamed module (i.e. the classpath), e.g. by pulling in thejakarta.annotation:jakarta.annotation-api dependency(there already is a transitive dependency to the Jakarta Annotations API when depending onorg.hibernate.validator:hibernate-validator-cdi). If you need to enable the |
Let’s dive directly into an example to see how to apply constraints.
package org.hibernate.validator.referenceguide.chapter01;import javax.validation.constraints.Min;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; @Min(2) private int seatCount; public Car(String manufacturer, String licencePlate, int seatCount) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; this.seatCount = seatCount; } //getters and setters ...}The@NotNull,@Size and@Min annotations are used to declare the constraints which should be appliedto the fields of a Car instance:
manufacturer must never benull
licensePlate must never benull and must be between 2 and 14 characters long
seatCount must be at least 2
You can find the complete source code of all examples used in this reference guide in the HibernateValidatorsource repositoryon GitHub. |
To perform a validation of these constraints, you use aValidator instance. Let’s have a look at aunit test forCar:
package org.hibernate.validator.referenceguide.chapter01;import java.util.Set;import javax.validation.ConstraintViolation;import javax.validation.Validation;import javax.validation.Validator;import javax.validation.ValidatorFactory;import org.junit.BeforeClass;import org.junit.Test;import static org.junit.Assert.assertEquals;public class CarTest { private static Validator validator; @BeforeClass public static void setUpValidator() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void manufacturerIsNull() { Car car = new Car( null, "DD-AB-123", 4 ); Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car ); assertEquals( 1, constraintViolations.size() ); assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() ); } @Test public void licensePlateTooShort() { Car car = new Car( "Morris", "D", 4 ); Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car ); assertEquals( 1, constraintViolations.size() ); assertEquals( "size must be between 2 and 14", constraintViolations.iterator().next().getMessage() ); } @Test public void seatCountTooLow() { Car car = new Car( "Morris", "DD-AB-123", 1 ); Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car ); assertEquals( 1, constraintViolations.size() ); assertEquals( "must be greater than or equal to 2", constraintViolations.iterator().next().getMessage() ); } @Test public void carIsValid() { Car car = new Car( "Morris", "DD-AB-123", 2 ); Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car ); assertEquals( 0, constraintViolations.size() ); }}In thesetUp() method aValidator object is retrieved from theValidatorFactory. AValidatorinstance is thread-safe and may be reused multiple times. It thus can safely be stored in a staticfield and be used in the test methods to validate the differentCar instances.
Thevalidate() method returns a set ofConstraintViolation instances, which you can iterate over inorder to see which validation errors occurred. The first three test methods show some expectedconstraint violations:
The@NotNull constraint onmanufacturer is violated inmanufacturerIsNull()
The@Size constraint onlicensePlate is violated inlicensePlateTooShort()
The@Min constraint onseatCount is violated inseatCountTooLow()
If the object validates successfully,validate() returns an empty set as you can see incarIsValid().
Note that only classes from the packagejavax.validation are used. These are provided from the BeanValidation API. No classes from Hibernate Validator are directly referenced, resulting in portablecode.
That concludes the 5 minutes tour through the world of Hibernate Validator and Jakarta Bean Validation.Continue exploring the code examples or look at further examples referenced inChapter 14,Further reading.
To learn more about the validation of beans and properties, just continue readingChapter 2,Declaring and validating bean constraints. If you are interested in using Jakarta Bean Validation for the validation ofmethod pre- and postcondition refer toChapter 3,Declaring and validating method constraints. In case your application hasspecific validation requirements have a look atChapter 6,Creating custom constraints.
In this chapter you will learn how to declare (seeSection 2.1, “Declaring bean constraints”) andvalidate (seeSection 2.2, “Validating bean constraints”) bean constraints.Section 2.3, “Built-in constraints” provides an overview of all built-in constraints coming withHibernate Validator.
If you are interested in applying constraints to method parameters and return values, refer toChapter 3,Declaring and validating method constraints.
Constraints in Jakarta Bean Validation are expressed via Java annotations. In this section you will learnhow to enhance an object model with these annotations. There are four types of bean constraints:
field constraints
property constraints
container element constraints
class constraints
Not all constraints can be placed on all of these levels. In fact, none of the default constraintsdefined by Jakarta Bean Validation can be placed at class level. The |
Constraints can be expressed by annotating a field of a class.Example 2.1, “Field-level constraints” shows a fieldlevel configuration example:
package org.hibernate.validator.referenceguide.chapter02.fieldlevel;public class Car { @NotNull private String manufacturer; @AssertTrue private boolean isRegistered; public Car(String manufacturer, boolean isRegistered) { this.manufacturer = manufacturer; this.isRegistered = isRegistered; } //getters and setters...}When using field-level constraints field access strategy is used to access the value to bevalidated. This means the validation engine directly accesses the instance variable and does notinvoke the property accessor method even if such an accessor exists.
Constraints can be applied to fields of any access type (public, private etc.). Constraints onstatic fields are not supported, though.
When validating byte code enhanced objects, property level constraints should be used, because thebyte code enhancing library won’t be able to determine a field access via reflection. |
If your model class adheres to theJavaBeans standard, itis also possible to annotate the properties of a bean class instead of its fields.Example 2.2, “Property-level constraints” uses the same entity as inExample 2.1, “Field-level constraints”, however, property levelconstraints are used.
package org.hibernate.validator.referenceguide.chapter02.propertylevel;public class Car { private String manufacturer; private boolean isRegistered; public Car(String manufacturer, boolean isRegistered) { this.manufacturer = manufacturer; this.isRegistered = isRegistered; } @NotNull public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } @AssertTrue public boolean isRegistered() { return isRegistered; } public void setRegistered(boolean isRegistered) { this.isRegistered = isRegistered; }}The property’s getter method has to be annotated, not its setter. That way also read-only propertiescan be constrained which have no setter method. |
When using property level constraints property access strategy is used to access the value to bevalidated, i.e. the validation engine accesses the state via the property accessor method.
It is recommended to stick either to fieldor property annotations within one class. It is notrecommended to annotate a fieldand the accompanying getter method as this would cause the fieldto be validated twice. |
It is possible to specify constraints directly on the type argument of aparameterized type: these constraints are called container element constraints.
This requires thatElementType.TYPE_USE is specified via@Targetin the constraint definition. As of Jakarta Bean Validation 2.0, built-in Jakarta Bean Validation as well asHibernate Validator specific constraints specifyElementType.TYPE_USE and can be useddirectly in this context.
Hibernate Validator validates container element constraints specified on the following standardJava containers:
implementations ofjava.util.Iterable (e.g.Lists,Sets),
implementations ofjava.util.Map, with support for keys and values,
java.util.Optional,java.util.OptionalInt,java.util.OptionalDouble,java.util.OptionalLong,
the various implementations of JavaFX’sjavafx.beans.observable.ObservableValue.
It also supports container element constraints on custom container types (seeChapter 7,Value extraction).
In versions prior to 6, a subset of container element constraints were supported. A |
We present below a couple of examples illustrating container element constraints on various Java types.
In these examples,@ValidPart is a custom constraint allowed to be used in theTYPE_USE context.
IterableWhen applying constraints on anIterable type argument, Hibernate Validator will validate eachelement.Example 2.3, “Container element constraint onSet” shows an example of aSet with a container element constraint.
Setpackage org.hibernate.validator.referenceguide.chapter02.containerelement.set;public class Car { private Set<@ValidPart String> parts = new HashSet<>(); public void addPart(String part) { parts.add( part ); } //...}Car car = new Car();car.addPart( "Wheel" );car.addPart( null );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "'null' is not a valid car part.", constraintViolation.getMessage());assertEquals( "parts[].<iterable element>", constraintViolation.getPropertyPath().toString() );Note how the property path clearly states that the violation comes from an element of the iterable.
ListWhen applying constraints on aList type argument, Hibernate Validator will validate eachelement.Example 2.4, “Container element constraint onList” shows an example of aList with a container element constraint.
Listpackage org.hibernate.validator.referenceguide.chapter02.containerelement.list;public class Car { private List<@ValidPart String> parts = new ArrayList<>(); public void addPart(String part) { parts.add( part ); } //...}Car car = new Car();car.addPart( "Wheel" );car.addPart( null );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "'null' is not a valid car part.", constraintViolation.getMessage());assertEquals( "parts[1].<list element>", constraintViolation.getPropertyPath().toString() );Here, the property path also contains the index of the invalid element.
MapContainer element constraints are also validated on map keys and values.Example 2.5, “Container element constraint on map keys and values” shows an example of aMap with a constraint on the keyand a constraint on the value.
package org.hibernate.validator.referenceguide.chapter02.containerelement.map;public class Car { public enum FuelConsumption { CITY, HIGHWAY } private Map<@NotNull FuelConsumption, @MaxAllowedFuelConsumption Integer> fuelConsumption = new HashMap<>(); public void setFuelConsumption(FuelConsumption consumption, int value) { fuelConsumption.put( consumption, value ); } //...}Car car = new Car();car.setFuelConsumption( Car.FuelConsumption.HIGHWAY, 20 );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "20 is outside the max fuel consumption.", constraintViolation.getMessage());assertEquals( "fuelConsumption[HIGHWAY].<map value>", constraintViolation.getPropertyPath().toString());Car car = new Car();car.setFuelConsumption( null, 5 );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "must not be null", constraintViolation.getMessage());assertEquals( "fuelConsumption<K>[].<map key>", constraintViolation.getPropertyPath().toString());The property paths of the violations are particularly interesting:
The key of the invalid element is included in the property path (in the second example, the key isnull).
In the first example, the violation concerns the<map value>, in the second one, the<map key>.
In the second example, you might have noticed the presence of the type argument<K>, more on this later.
java.util.OptionalWhen applying a constraint on the type argument ofOptional, Hibernate Validator will automaticallyunwrap the type and validate the internal value.Example 2.6, “Container element constraint on Optional” showsan example of anOptional with a container element constraint.
package org.hibernate.validator.referenceguide.chapter02.containerelement.optional;public class Car { private Optional<@MinTowingCapacity(1000) Integer> towingCapacity = Optional.empty(); public void setTowingCapacity(Integer alias) { towingCapacity = Optional.of( alias ); } //...}Car car = new Car();car.setTowingCapacity( 100 );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "Not enough towing capacity.", constraintViolation.getMessage());assertEquals( "towingCapacity", constraintViolation.getPropertyPath().toString());Here, the property path only contains the name of the property as we are consideringOptional as a "transparent"container.
Container element constraints can also be used with custom containers.
AValueExtractor must be registered for the custom type allowing to retrievethe value(s) to validate (seeChapter 7,Value extraction for more information about how to implementyour ownValueExtractor and how to register it).
Example 2.7, “Container element constraint on custom container type” shows an example of a customparameterized type with a type argument constraint.
package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;public class Car { private GearBox<@MinTorque(100) Gear> gearBox; public void setGearBox(GearBox<Gear> gearBox) { this.gearBox = gearBox; } //...}package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;public class GearBox<T extends Gear> { private final T gear; public GearBox(T gear) { this.gear = gear; } public Gear getGear() { return this.gear; }}package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;public class Gear { private final Integer torque; public Gear(Integer torque) { this.torque = torque; } public Integer getTorque() { return torque; } public static class AcmeGear extends Gear { public AcmeGear() { super( 60 ); } }}package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;public class GearBoxValueExtractor implements ValueExtractor<GearBox<@ExtractedValue ?>> { @Override public void extractValues(GearBox<@ExtractedValue ?> originalValue, ValueExtractor.ValueReceiver receiver) { receiver.value( null, originalValue.getGear() ); }}Car car = new Car();car.setGearBox( new GearBox<>( new Gear.AcmeGear() ) );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();assertEquals( "Gear is not providing enough torque.", constraintViolation.getMessage());assertEquals( "gearBox", constraintViolation.getPropertyPath().toString());Constraints are also supported on nested container elements.
When validating aCar object as presented inExample 2.8, “Constraints on nested container elements”, both the@NotNullconstraints onPart andManufacturer will be enforced.
package org.hibernate.validator.referenceguide.chapter02.containerelement.nested;public class Car { private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers = new HashMap<>(); //...}Last but not least, a constraint can also be placed on the class level. In this case not a singleproperty is subject of the validation but the complete object. Class-level constraints are useful ifthe validation depends on a correlation between several properties of an object.
TheCar class inExample 2.9, “Class-level constraint” has the two attributesseatCount andpassengers and itshould be ensured that the list of passengers does not have more entries than available seats. Forthat purpose the@ValidPassengerCount constraint is added on the class level. The validator of thatconstraint has access to the completeCar object, allowing to compare the numbers of seats andpassengers.
Refer toSection 6.2, “Class-level constraints” to learn in detail how to implement this customconstraint.
package org.hibernate.validator.referenceguide.chapter02.classlevel;@ValidPassengerCountpublic class Car { private int seatCount; private List<Person> passengers; //...}When a class implements an interface or extends another class, all constraint annotations declaredon the super-type apply in the same manner as the constraints specified on the class itself. To makethings clearer let’s have a look at the following example:
package org.hibernate.validator.referenceguide.chapter02.inheritance;public class Car { private String manufacturer; @NotNull public String getManufacturer() { return manufacturer; } //...}package org.hibernate.validator.referenceguide.chapter02.inheritance;public class RentalCar extends Car { private String rentalStation; @NotNull public String getRentalStation() { return rentalStation; } //...}Here the classRentalCar is a subclass ofCar and adds the propertyrentalStation. If an instance ofRentalCar is validated, not only the@NotNull constraint onrentalStation is evaluated, but also theconstraint onmanufacturer from the parent class.
The same would be true, ifCar was not a superclass but an interface implemented byRentalCar.
Constraint annotations are aggregated if methods are overridden. So ifRentalCar overrode thegetManufacturer() method fromCar, any constraints annotated at the overriding method would beevaluated in addition to the@NotNull constraint from the superclass.
The Jakarta Bean Validation API does not only allow to validate single class instances but also completeobject graphs (cascaded validation). To do so, just annotate a field or property representing areference to another object with@Valid as demonstrated inExample 2.11, “Cascaded validation”.
package org.hibernate.validator.referenceguide.chapter02.objectgraph;public class Car { @NotNull @Valid private Person driver; //...}package org.hibernate.validator.referenceguide.chapter02.objectgraph;public class Person { @NotNull private String name; //...}If an instance ofCar is validated, the referencedPerson object will be validated as well, as thedriver field is annotated with@Valid. Therefore the validation of aCar will fail if thename fieldof the referencedPerson instance isnull.
The validation of object graphs is recursive, i.e. if a reference marked for cascaded validationpoints to an object which itself has properties annotated with@Valid, these references will befollowed up by the validation engine as well. The validation engine will ensure that no infiniteloops occur during cascaded validation, for example if two objects hold references to each other.
Note thatnull values are getting ignored during cascaded validation.
As constraints, object graph validation also works for container elements. That means any type argumentof a container can be annotated with@Valid, which will cause each contained element to be validated when theparent object is validated.
Cascaded validation is also supported for nested container elements. |
package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;public class Car { private List<@NotNull @Valid Person> passengers = new ArrayList<Person>(); private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers = new HashMap<>(); //...}package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;public class Part { @NotNull private String name; //...}package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;public class Manufacturer { @NotNull private String name; //...}When validating an instance of theCar class shown inExample 2.12, “Cascaded validation of containers”, aConstraintViolation will be created:
if any of thePerson objects contained in the passengers list has anull name;
if any of thePart objects contained in the map keys has anull name;
if any of theManufacturer objects contained in the list nested in the map valueshas anull name.
In versions prior to 6, Hibernate Validator supported cascaded validation for a subset of container elementsand it was implemented at the container level (e.g. you would use This is still supported but is not recommended. Please use container element level |
TheValidator interface is the most important object in Jakarta Bean Validation. The next section shows howto obtain aValidator instance. Afterwards you’ll learn how to use the different methods of theValidator interface.
Validator instanceThe first step towards validating an entity instance is to get hold of aValidator instance. Theroad to this instance leads via theValidation class and aValidatorFactory. The easiest way is touse the static methodValidation#buildDefaultValidatorFactory():
Validation#buildDefaultValidatorFactory()ValidatorFactory factory = Validation.buildDefaultValidatorFactory();validator = factory.getValidator();This bootstraps a validator in the default configuration. Refer toChapter 9,Bootstrapping tolearn more about the different bootstrapping methods and how to obtain a specifically configuredValidator instance.
TheValidator interface contains three methods that can be used to either validate entire entitiesor just single properties of the entity.
All three methods return aSet<ConstraintViolation>. The set is empty, if the validation succeeds.Otherwise aConstraintViolation instance is added for each violated constraint.
All the validation methods have a var-args parameter which can be used to specify which validationgroups shall be considered when performing the validation. If the parameter is not specified, thedefault validation group (javax.validation.groups.Default) is used. The topic of validation groupsis discussed in detail inChapter 5,Grouping constraints.
Validator#validate()Use thevalidate() method to perform validation of all constraints of a given bean.Example 2.14, “UsingValidator#validate()” shows the validation of an instance of theCar class fromExample 2.2, “Property-level constraints” which fails to satisfy the@NotNull constraint on themanufacturerproperty. The validation call therefore returns oneConstraintViolation object.
Validator#validate()Car car = new Car( null, true );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );Validator#validateProperty()With help of thevalidateProperty() you can validate a single named property of a given object. Theproperty name is the JavaBeans property name.
Validator#validateProperty()Car car = new Car( null, true );Set<ConstraintViolation<Car>> constraintViolations = validator.validateProperty( car, "manufacturer");assertEquals( 1, constraintViolations.size() );assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );Validator#validateValue()By using thevalidateValue() method you can check whether a single property of a given class can bevalidated successfully, if the property had the specified value:
Validator#validateValue()Set<ConstraintViolation<Car>> constraintViolations = validator.validateValue( Car.class, "manufacturer", null);assertEquals( 1, constraintViolations.size() );assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
|
Validator#validateProperty() is for example used in the integration of Jakarta Bean Validation into JSF 2(seeSection 11.2, “JSF & Seam”) to perform a validation of the values entered into a formbefore they are propagated to the model.
ConstraintViolationConstraintViolation methodsNow it is time to have a closer look at what aConstraintViolation is.Using the different methods ofConstraintViolation a lot of useful information about the cause of the validation failure can be determined.The following gives an overview of these methods.The values under "Example" column refer toExample 2.14, “UsingValidator#validate()”.
getMessage()The interpolated error message
"must not be null"
getMessageTemplate()The non-interpolated error message
"{… NotNull.message}"
getRootBean()The root bean being validated
car
getRootBeanClass()The class of the root bean being validated
Car.class
getLeafBean()If a bean constraint, the bean instance the constraint isapplied on; if a property constraint, the bean instance hostingthe property the constraint is applied on
car
getPropertyPath()The property path to the validated value from root bean
contains one node with kindPROPERTY and name "manufacturer"
getInvalidValue()The value failing to pass the constraint
null
getConstraintDescriptor()Constraint metadata reported to fail
descriptor for@NotNull
To determine the element that triggered the violation, you need to exploit the result of thegetPropertyPath()method.
The returnedPath is composed ofNodes describing the path to the element.
More information about the structure of thePath and the various types ofNodes can be found intheConstraintViolation section of theJakarta Bean Validation specification.
Hibernate Validator comprises a basic set of commonly used constraints. These are foremost theconstraints defined by the Jakarta Bean Validation specification (seeSection 2.3.1, “Jakarta Bean Validation constraints”).Additionally, Hibernate Validator provides useful custom constraints (seeSection 2.3.2, “Additional constraints”).
Below you can find a list of all constraints specified in the Jakarta Bean Validation API.All these constraints apply to the field/property level, there are no class-level constraints defined in the Jakarta Bean Validation specification.If you are using the Hibernate object-relational mapper, some of the constraints are taken into account when creating the DDL for your model (see "Hibernate metadata impact").
Hibernate Validator allows some constraints to be applied to more data types than required by theJakarta Bean Validation specification (e.g. |
@AssertFalseChecks that the annotated element is false
Boolean,boolean
None
@AssertTrueChecks that the annotated element is true
Boolean,boolean
None
@DecimalMax(value=, inclusive=)Checks whether the annotated value is less than the specified maximum, wheninclusive=false. Otherwise whether the value is less than or equal to the specified maximum. The parameter value is the string representation of the max value according to theBigDecimal string representation.
BigDecimal,BigInteger,CharSequence,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofNumber andjavax.money.MonetaryAmount (if theJSR 354 API and an implementation is on the class path)
None
@DecimalMin(value=, inclusive=)Checks whether the annotated value is larger than the specified minimum, wheninclusive=false. Otherwise whether the value is larger than or equal to the specified minimum. The parameter value is the string representation of the min value according to theBigDecimal string representation.
BigDecimal,BigInteger,CharSequence,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofNumber andjavax.money.MonetaryAmount
None
@Digits(integer=, fraction=)Checks whether the annotated value is a number having up tointeger digits andfraction fractional digits
BigDecimal,BigInteger,CharSequence,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofNumber andjavax.money.MonetaryAmount
Defines column precision and scale
@EmailChecks whether the specified character sequence is a valid email address. The optional parametersregexp andflags allow to specify an additional regular expression (including regular expression flags) which the email must match.
CharSequence
None
@FutureChecks whether the annotated date is in the future
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; additionally supported by HV, if theJoda Time date/time API is on the classpath: any implementations ofReadablePartial andReadableInstant
None
@FutureOrPresentChecks whether the annotated date is in the present or in the future
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; additionally supported by HV, if theJoda Time date/time API is on the classpath: any implementations ofReadablePartial andReadableInstant
None
@Max(value=)Checks whether the annotated value is less than or equal to the specified maximum
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
Adds a check constraint on the column
@Min(value=)Checks whether the annotated value is higher than or equal to the specified minimum
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
Adds a check constraint on the column
@NotBlankChecks that the annotated character sequence is not null and the trimmed length is greater than 0. The difference to@NotEmpty is that this constraint can only be applied on character sequences and that trailing white-spaces are ignored.
CharSequence
None
@NotEmptyChecks whether the annotated element is not null nor empty
CharSequence,Collection,Map and arrays
None
@NotNullChecks that the annotated value is notnull
Any type
Column(s) are not nullable
@NegativeChecks if the element is strictly negative. Zero values are considered invalid.
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
None
@NegativeOrZeroChecks if the element is negative or zero.
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
None
@NullChecks that the annotated value isnull
Any type
None
@PastChecks whether the annotated date is in the past
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; Additionally supported by HV, if theJoda Time date/time API is on the classpath: any implementations ofReadablePartial andReadableInstant
None
@PastOrPresentChecks whether the annotated date is in the past or in the present
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; Additionally supported by HV, if theJoda Time date/time API is on the classpath: any implementations ofReadablePartial andReadableInstant
None
@Pattern(regex=, flags=)Checks if the annotated string matches the regular expressionregex considering the given flagmatch
CharSequence
None
@PositiveChecks if the element is strictly positive. Zero values are considered invalid.
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
None
@PositiveOrZeroChecks if the element is positive or zero.
BigDecimal,BigInteger,byte,short,int,long and the respective wrappers of the primitive types; additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type ofNumber andjavax.money.MonetaryAmount
None
@Size(min=, max=)Checks if the annotated element’s size is betweenmin andmax (inclusive)
CharSequence,Collection,Map and arrays
Column length will be set tomax
On top of the parameters listed above each constraint has the parametersmessage, groups and payload. This is a requirement of the Jakarta Bean Validation specification. |
In addition to the constraints defined by the Jakarta Bean Validation API, Hibernate Validator provides several useful custom constraints which are listed below.With one exception also these constraints apply to the field/property level, only@ScriptAssert is a class-level constraint.
@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! See alsoAnatomy of a credit card number.ignoreNonDigitCharacters allows to ignore non digit characters. The default isfalse.
CharSequence
None
@Currency(value=)Checks that the currency unit of the annotatedjavax.money.MonetaryAmount is part of the specified currency units.
any sub-type ofjavax.money.MonetaryAmount (if theJSR 354 API and an implementation is on the class path)
None
@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)Checks that annotatedjava.time.Duration element is not greater than the one constructed from annotation parameters. Equality is allowed ifinclusive flag is set totrue.
java.time.Duration
None
@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)Checks that annotatedjava.time.Duration element is not less than the one constructed from annotation parameters. Equality is allowed ifinclusive flag is set totrue.
java.time.Duration
None
@EANChecks that the annotated character sequence is a validEAN barcode. type determines the type of barcode. The default is EAN-13.
CharSequence
None
@ISBNChecks that the annotated character sequence is a validISBN.type determines the type of ISBN. The default is ISBN-13.
CharSequence
None
@Length(min=, max=)Validates that the annotated character sequence is betweenmin andmax included
CharSequence
Column length will be set to max
@CodePointLength(min=, max=, normalizationStrategy=)Validates that code point length of the annotated character sequence is betweenmin andmax included. Validates normalized value ifnormalizationStrategy is set.
CharSequence
None
@LuhnCheck(startIndex= , endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)Checks that the digits within the annotated character sequence pass the Luhn checksum algorithm (see alsoLuhn algorithm).startIndex andendIndex allow to only run the algorithm on the specified sub-string.checkDigitIndex allows to use an arbitrary digit within the character sequence as the check digit. If not specified it is assumed that the check digit is part of the specified range. Last but not least,ignoreNonDigitCharacters allows to ignore non digit characters.
CharSequence
None
@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)Checks that the digits within the annotated character sequence pass the generic mod 10 checksum algorithm.multiplier determines the multiplier for odd numbers (defaults to 3),weight the weight for even numbers (defaults to 1).startIndex andendIndex allow to only run the algorithm on the specified sub-string.checkDigitIndex allows to use an arbitrary digit within the character sequence as the check digit. If not specified it is assumed that the check digit is part of the specified range. Last but not least,ignoreNonDigitCharacters allows to ignore non digit characters.
CharSequence
None
@Mod11Check(threshold=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=, treatCheck10As=, treatCheck11As=)Checks that the digits within the annotated character sequence pass the mod 11 checksum algorithm.threshold specifies the threshold for the mod11 multiplier growth; if no value is specified the multiplier will grow indefinitely.treatCheck10As andtreatCheck11As specify the check digits to be used when the mod 11 checksum equals 10 or 11, respectively. Default to X and 0, respectively.startIndex,endIndexcheckDigitIndex andignoreNonDigitCharacters carry the same semantics as in@Mod10Check.
CharSequence
None
@Normalized(form=)Validates that the annotated character sequence is normalized according to the givenform.
CharSequence
None
@Range(min=, max=)Checks whether the annotated value lies between (inclusive) the specified minimum and maximum
BigDecimal,BigInteger,CharSequence,byte,short,int,long and the respective wrappers of the primitive types
None
@ScriptAssert(lang=, script=, alias=, reportOn=)Checks whether the given script can successfully be evaluated against the annotated element. In order to use this constraint, an implementation of the Java Scripting API as defined by JSR 223 ("Scripting for the JavaTM Platform") must be a part of the class path. The expressions to be evaluated can be written in any scripting or expression language, for which a JSR 223 compatible engine can be found in the class path. Even though this is a class-level constraint, one can use thereportOn attribute to report a constraint violation on a specific property rather than the whole object.
Any type
None
@UniqueElementsChecks that the annotated collection only contains unique elements. The equality is determined using theequals() method. The default message does not include the list of duplicate elements but you can include it by overriding the message and using the{duplicates} message parameter. The list of duplicate elements is also included in the dynamic payload of the constraint violation.
Collection
None
@URL(protocol=, host=, port=, regexp=, flags=)Checks if the annotated character sequence is a valid URL according to RFC2396. If any of the optional parametersprotocol,host orport are specified, the corresponding URL fragments must match the specified values. The optional parametersregexp andflags allow to specify an additional regular expression (including regular expression flags) which the URL must match. Per default this constraint used thejava.net.URL constructor to verify whether a given string represents a valid URL. A regular expression based version is also available -RegexpURLValidator - which can be configured via XML (seeSection 8.2, “Mapping constraints viaconstraint-mappings”) or the programmatic API (seeSection 12.15.2, “Adding constraint definitions programmatically”).
CharSequence
None
Hibernate Validator offers also some country specific constraints, e.g. for the validation of socialsecurity numbers.
If you have to implement a country specific constraint, consider making it a contribution toHibernate Validator! |
@CNPJChecks that the annotated character sequence represents a Brazilian corporate tax payer registry number (Cadastro de Pessoa Jurídica)
CharSequence
None
Brazil
@CPFChecks that the annotated character sequence represents a Brazilian individual taxpayer registry number (Cadastro de Pessoa Física)
CharSequence
None
Brazil
@TituloEleitoralChecks that the annotated character sequence represents a Brazilian voter ID card number (Título Eleitoral)
CharSequence
None
Brazil
@NIPChecks that the annotated character sequence represents a Polish VAT identification number (NIP)
CharSequence
None
Poland
@PESELChecks that the annotated character sequence represents a Polish national identification number (PESEL)
CharSequence
None
Poland
@REGONChecks that the annotated character sequence represents a Polish taxpayer identification number (REGON). Can be applied to both 9 and 14 digits versions of REGON
CharSequence
None
Poland
@INNChecks that the annotated character sequence represents a Russian taxpayer identification number (INN). Can be applied to both individual and juridical versions of INN
CharSequence
None
Russia
In some cases neither the Jakarta Bean Validation constraints nor the custom constraints provided byHibernate Validator will fulfill your requirements. In this case you can easily write your ownconstraint. You can find more information inChapter 6,Creating custom constraints. |
As of Bean Validation 1.1, constraints can not only be applied to JavaBeans and their properties,but also to the parameters and return values of the methods and constructors of any Java type. Thatway Jakarta Bean Validation constraints can be used to specify
the preconditions that must be satisfied by the caller before a method or constructor may beinvoked (by applying constraints to the parameters of an executable)
the postconditions that are guaranteed to the caller after a method or constructor invocationreturns (by applying constraints to the return value of an executable)
For the purpose of this reference guide, the termmethod constraint refers to both, method andconstructor constraints, if not stated otherwise. Occasionally, the termexecutable is used whenreferring to methods and constructors. |
This approach has several advantages over traditional ways of checking the correctness of parametersand return values:
the checks don’t have to be performed manually (e.g. by throwingIllegalArgumentException orsimilar), resulting in less code to write and maintain
an executable’s pre- and postconditions don’t have to be expressed again in its documentation,since the constraint annotations will automatically be included in the generated JavaDoc. Thisavoids redundancies and reduces the chance of inconsistencies between implementation anddocumentation
In order to make annotations show up in the JavaDoc of annotated elements, the annotation typesthemselves must be annotated with the meta annotation @Documented. This is the case for all built-inconstraints and is considered a best practice for any custom constraints. |
In the remainder of this chapter you will learn how to declare parameter and return valueconstraints and how to validate them using theExecutableValidator API.
You specify the preconditions of a method or constructor by adding constraint annotations to itsparameters as demonstrated inExample 3.1, “Declaring method and constructor parameter constraints”.
package org.hibernate.validator.referenceguide.chapter03.parameter;public class RentalStation { public RentalStation(@NotNull String name) { //... } public void rentCar( @NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays) { //... }}The following preconditions are declared here:
Thename passed to theRentalCar constructor must not benull
When invoking therentCar() method, the givencustomer must not benull, the rental’s startdate must not benull as well as be in the future and finally the rental duration must be at leastone day
Note that declaring method or constructor constraints itself does not automatically cause theirvalidation upon invocation of the executable. Instead, theExecutableValidator API (seeSection 3.2, “Validating method constraints”) must be used to perform the validation, which isoften done using a method interception facility such as AOP, proxy objects etc.
Constraints may only be applied to instance methods, i.e. declaring constraints on static methods isnot supported. Depending on the interception facility you use for triggering method validation,additional restrictions may apply, e.g. with respect to the visibility of methods supported astarget of interception. Refer to the documentation of the interception technology to find outwhether any such limitations exist.
Sometimes validation does not only depend on a single parameter but on several or even allparameters of a method or constructor. This kind of requirement can be fulfilled with help of across-parameter constraint.
Cross-parameter constraints can be considered as the method validation equivalent to class-levelconstraints. Both can be used to implement validation requirements which are based on severalelements. While class-level constraints apply to several properties of a bean, cross-parameterconstraints apply to several parameters of an executable.
In contrast to single-parameter constraints, cross-parameter constraints are declared on the methodor constructor as you can see inExample 3.2, “Declaring a cross-parameter constraint”. Here the cross-parameter constraint@LuggageCountMatchesPassengerCount declared on theload() method is used toensure that no passenger has more than two pieces of luggage.
package org.hibernate.validator.referenceguide.chapter03.crossparameter;public class Car { @LuggageCountMatchesPassengerCount(piecesOfLuggagePerPassenger = 2) public void load(List<Person> passengers, List<PieceOfLuggage> luggage) { //... }}As you will learn in the next section, return value constraints are also declared on the methodlevel. In order to distinguish cross-parameter constraints from return value constraints, theconstraint target is configured in theConstraintValidator implementation using the@SupportedValidationTarget annotation. You can find out about the details inSection 6.3, “Cross-parameter constraints” which shows how to implement your own cross-parameter constraint.
In some cases a constraint can be applied to an executable’s parameters (i.e. it is a cross-parameter constraint), but also to the return value. One example for this are custom constraintswhich allow to specify validation rules using expression or script languages.
Such constraints must define a membervalidationAppliesTo() which can be used at declaration time tospecify the constraint target. As shown inExample 3.3, “Specifying a constraint’s target” you apply theconstraint to an executable’s parameters by specifyingvalidationAppliesTo = ConstraintTarget.PARAMETERS, whileConstraintTarget.RETURN_VALUE is usedto apply the constraint to the executable return value.
package org.hibernate.validator.referenceguide.chapter03.crossparameter.constrainttarget;public class Garage { @ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.PARAMETERS) public Car buildCar(List<Part> parts) { //... return null; } @ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.RETURN_VALUE) public Car paintCar(int color) { //... return null; }}Although such a constraint is applicable to the parameters and return value of an executable, thetarget can often be inferred automatically. This is the case, if the constraint is declared on
a void method with parameters (the constraint applies to the parameters)
an executable with return value but no parameters (the constraint applies to the return value)
neither a method nor a constructor, but a field, parameter etc. (the constraint applies to theannotated element)
In these situations you don’t have to specify the constraint target. It is still recommended to doso if it increases readability of the source code. If the constraint target is not specified insituations where it can’t be determined automatically, aConstraintDeclarationException is raised.
The postconditions of a method or constructor are declared by adding constraint annotations to theexecutable as shown inExample 3.4, “Declaring method and constructor return value constraints”.
package org.hibernate.validator.referenceguide.chapter03.returnvalue;public class RentalStation { @ValidRentalStation public RentalStation() { //... } @NotNull @Size(min = 1) public List<@NotNull Customer> getCustomers() { //... return null; }}The following constraints apply to the executables ofRentalStation:
Any newly createdRentalStation object must satisfy the@ValidRentalStation constraint
The customer list returned bygetCustomers() must not benull and must contain at least on element
The customer list returned bygetCustomers() must no containnull objects
As you can see in the above example, container element constraints are supported on method return value.They are also supported on method parameters. |
Similar to the cascaded validation of JavaBeans properties (seeSection 2.1.6, “Object graphs”), the@Valid annotation can be used to mark executableparameters and return values for cascaded validation. When validating a parameter or return valueannotated with@Valid, the constraints declared on the parameter or return value object arevalidated as well.
InExample 3.5, “Marking executable parameters and return values for cascaded validation”, thecar parameter of the methodGarage#checkCar() aswell as the return value of theGarage constructor are marked for cascaded validation.
package org.hibernate.validator.referenceguide.chapter03.cascaded;public class Garage { @NotNull private String name; @Valid public Garage(String name) { this.name = name; } public boolean checkCar(@Valid @NotNull Car car) { //... return false; }}package org.hibernate.validator.referenceguide.chapter03.cascaded;public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; public Car(String manufacturer, String licencePlate) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; } //getters and setters ...}When validating the arguments of thecheckCar() method, the constraints on the properties of thepassedCar object are evaluated as well. Similarly, the@NotNull constraint on the name field ofGarage is checked when validating the return value of theGarage constructor.
Generally, the cascaded validation works for executables in exactly the same way as it does forJavaBeans properties.
In particular,null values are ignored during cascaded validation (naturally this can’t happenduring constructor return value validation) and cascaded validation is performed recursively, i.e.if a parameter or return value object which is marked for cascaded validation itself has propertiesmarked with@Valid, the constraints declared on the referenced elements will be validated as well.
Same as for fields and properties, cascaded validation can also be declared on containerelements (e.g. elements of collections, maps or custom containers) of return values and parameters.
In this case, each element contained by the container gets validated.So when validating the arguments of thecheckCars() method inExample 3.6, “Container elements of method parameter marked for cascaded validation”, each element instance of the passed listwill be validated and aConstraintViolation created when any of the containedCar instances is invalid.
package org.hibernate.validator.referenceguide.chapter03.cascaded.containerelement;public class Garage { public boolean checkCars(@NotNull List<@Valid Car> cars) { //... return false; }}When declaring method constraints in inheritance hierarchies, it is important to be aware of thefollowing rules:
The preconditions to be satisfied by the caller of a method may not be strengthened in subtypes
The postconditions guaranteed to the caller of a method may not be weakened in subtypes
These rules are motivated by the concept ofbehavioral subtyping which requires that wherever atypeT is used, also a subtypeS ofT may be used without altering the program’s behavior.
As an example, consider a class invoking a method on an object with the static typeT. If theruntime type of that object wasS andS imposed additional preconditions, the client class mightfail to satisfy these preconditions as is not aware of them. The rules of behavioral subtyping arealso known as theLiskovsubstitution principle.
The Jakarta Bean Validation specification implements the first rule by disallowing parameter constraints onmethods which override or implement a method declared in a supertype (superclass or interface).Example 3.7, “Illegal method parameter constraint in subtype” shows a violation of this rule.
package org.hibernate.validator.referenceguide.chapter03.inheritance.parameter;public interface Vehicle { void drive(@Max(75) int speedInMph);}package org.hibernate.validator.referenceguide.chapter03.inheritance.parameter;public class Car implements Vehicle { @Override public void drive(@Max(55) int speedInMph) { //... }}The@Max constraint onCar#drive() is illegal since this method implements the interface methodVehicle#drive(). Note that parameter constraints on overriding methods are also disallowed, if thesupertype method itself doesn’t declare any parameter constraints.
Furthermore, if a method overrides or implements a method declared in several parallel supertypes(e.g. two interfaces not extending each other or a class and an interface not implemented by thatclass), no parameter constraints may be specified for the method in any of the involved types. Thetypes inExample 3.8, “Illegal method parameter constraint in parallel types of a hierarchy” demonstrate a violation of thatrule. The methodRacingCar#drive() overridesVehicle#drive() as well asCar#drive().Therefore the constraint onVehicle#drive() is illegal.
package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;public interface Vehicle { void drive(@Max(75) int speedInMph);}package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;public interface Car { void drive(int speedInMph);}package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;public class RacingCar implements Car, Vehicle { @Override public void drive(int speedInMph) { //... }}The previously described restrictions only apply to parameter constraints. In contrast, return valueconstraints may be added in methods overriding or implementing any supertype methods.
In this case, all the method’s return value constraints apply for the subtype method, i.e. theconstraints declared on the subtype method itself as well as any return value constraints onoverridden/implemented supertype methods. This is legal as putting additional return valueconstraints in place may never represent a weakening of the postconditions guaranteed to the callerof a method.
So when validating the return value of the methodCar#getPassengers() shown inExample 3.9, “Return value constraints on supertype and subtype method”, the@Size constraint on the method itself as wellas the@NotNull constraint on the implemented interface methodVehicle#getPassengers() apply.
package org.hibernate.validator.referenceguide.chapter03.inheritance.returnvalue;public interface Vehicle { @NotNull List<Person> getPassengers();}package org.hibernate.validator.referenceguide.chapter03.inheritance.returnvalue;public class Car implements Vehicle { @Override @Size(min = 1) public List<Person> getPassengers() { //... return null; }}If the validation engine detects a violation of any of the aforementioned rules, aConstraintDeclarationException will be raised.
The rules described in this section only apply to methods but not constructors. By definition,constructors never override supertype constructors. Therefore, when validating the parameters or thereturn value of a constructor invocation only the constraints declared on the constructor itselfapply, but never any constraints declared on supertype constructors. |
Enforcement of these rules may be relaxed by setting the configuration parameters contained inthe |
The validation of method constraints is done using theExecutableValidator interface.
InSection 3.2.1, “Obtaining anExecutableValidator instance” you will learn how to obtain anExecutableValidatorinstance whileSection 3.2.2, “ExecutableValidator methods” shows how to use the different methodsoffered by this interface.
Instead of calling theExecutableValidator methods directly from within application code, they areusually invoked via a method interception technology such as AOP, proxy objects, etc. This causesexecutable constraints to be validated automatically and transparently upon method or constructorinvocation. Typically aConstraintViolationException is raised by the integration layer in case anyof the constraints is violated.
ExecutableValidator instanceYou can retrieve anExecutableValidator instance viaValidator#forExecutables() as shown inExample 3.10, “Obtaining anExecutableValidator instance”.
ExecutableValidator instanceValidatorFactory factory = Validation.buildDefaultValidatorFactory();executableValidator = factory.getValidator().forExecutables();In the example the executable validator is retrieved from the default validator factory, but ifrequired you could also bootstrap a specifically configured factory as described inChapter 9,Bootstrapping, for instance in order to use a specific parameter name provider(seeSection 9.2.4, “ParameterNameProvider”).
ExecutableValidator methodsTheExecutableValidator interface offers altogether four methods:
validateParameters() andvalidateReturnValue() for method validation
validateConstructorParameters() andvalidateConstructorReturnValue() for constructor validation
Just as the methods onValidator, all these methods return aSet<ConstraintViolation> which containsaConstraintViolation instance for each violated constraint and which is empty if the validationsucceeds. Also all the methods have a var-args groups parameter by which you can pass the validationgroups to be considered for validation.
The examples in the following sections are based on the methods on constructors of theCar classshown inExample 3.11, “ClassCar with constrained methods and constructors”.
Car with constrained methods and constructorspackage org.hibernate.validator.referenceguide.chapter03.validation;public class Car { public Car(@NotNull String manufacturer) { //... } @ValidRacingCar public Car(String manufacturer, String team) { //... } public void drive(@Max(75) int speedInMph) { //... } @Size(min = 1) public List<Passenger> getPassengers() { //... return Collections.emptyList(); }}ExecutableValidator#validateParameters()The methodvalidateParameters() is used to validate the arguments of a method invocation.Example 3.12, “UsingExecutableValidator#validateParameters()” shows an example. The validation results in aviolation of the@Max constraint on the parameter of thedrive() method.
ExecutableValidator#validateParameters()Car object = new Car( "Morris" );Method method = Car.class.getMethod( "drive", int.class );Object[] parameterValues = { 80 };Set<ConstraintViolation<Car>> violations = executableValidator.validateParameters( object, method, parameterValues);assertEquals( 1, violations.size() );Class<? extends Annotation> constraintType = violations.iterator() .next() .getConstraintDescriptor() .getAnnotation() .annotationType();assertEquals( Max.class, constraintType );Note thatvalidateParameters() validates all the parameter constraints of a method, i.e. constraintson individual parameters as well as cross-parameter constraints.
ExecutableValidator#validateReturnValue()UsingvalidateReturnValue() the return value of a method can be validated. The validation inExample 3.13, “UsingExecutableValidator#validateReturnValue()” yields one constraint violation since thegetPassengers() method is expected to return at least onePassenger instance.
ExecutableValidator#validateReturnValue()Car object = new Car( "Morris" );Method method = Car.class.getMethod( "getPassengers" );Object returnValue = Collections.<Passenger>emptyList();Set<ConstraintViolation<Car>> violations = executableValidator.validateReturnValue( object, method, returnValue);assertEquals( 1, violations.size() );Class<? extends Annotation> constraintType = violations.iterator() .next() .getConstraintDescriptor() .getAnnotation() .annotationType();assertEquals( Size.class, constraintType );ExecutableValidator#validateConstructorParameters()The arguments of constructor invocations can be validated withvalidateConstructorParameters() asshown in methodExample 3.14, “UsingExecutableValidator#validateConstructorParameters()”. Due to the@NotNull constraint on themanufacturer parameter, the validation call returns one constraintviolation.
ExecutableValidator#validateConstructorParameters()Constructor<Car> constructor = Car.class.getConstructor( String.class );Object[] parameterValues = { null };Set<ConstraintViolation<Car>> violations = executableValidator.validateConstructorParameters( constructor, parameterValues);assertEquals( 1, violations.size() );Class<? extends Annotation> constraintType = violations.iterator() .next() .getConstraintDescriptor() .getAnnotation() .annotationType();assertEquals( NotNull.class, constraintType );ExecutableValidator#validateConstructorReturnValue()Finally, by usingvalidateConstructorReturnValue() you can validate a constructor’s return value. InExample 3.15, “UsingExecutableValidator#validateConstructorReturnValue()”,validateConstructorReturnValue()returns one constraint violation, since theCar instance returned by the constructor doesn’t satisfythe@ValidRacingCar constraint (not shown).
ExecutableValidator#validateConstructorReturnValue()//constructor for creating racing carsConstructor<Car> constructor = Car.class.getConstructor( String.class, String.class );Car createdObject = new Car( "Morris", null );Set<ConstraintViolation<Car>> violations = executableValidator.validateConstructorReturnValue( constructor, createdObject);assertEquals( 1, violations.size() );Class<? extends Annotation> constraintType = violations.iterator() .next() .getConstraintDescriptor() .getAnnotation() .annotationType();assertEquals( ValidRacingCar.class, constraintType );ConstraintViolation methods for method validationIn addition to the methods introduced inSection 2.2.3, “ConstraintViolation”,ConstraintViolation provides two more methods specific to the validation of executable parametersand return values.
ConstraintViolation#getExecutableParameters() returns the validated parameter array in case ofmethod or constructor parameter validation, whileConstraintViolation#getExecutableReturnValue()provides access to the validated object in case of return value validation.
All the otherConstraintViolation methods generally work for method validation in the same way asfor validation of beans. Refer to theJavaDocto learn more about the behavior of the individual methods and their return values during bean andmethod validation.
Note thatgetPropertyPath() can be very useful in order to obtain detailed information about thevalidated parameter or return value, e.g. for logging purposes. In particular, you can retrieve nameand argument types of the concerned method as well as the index of the concerned parameter from thepath nodes. How this can be done is shown inExample 3.16, “Retrieving method and parameter information”.
Car object = new Car( "Morris" );Method method = Car.class.getMethod( "drive", int.class );Object[] parameterValues = { 80 };Set<ConstraintViolation<Car>> violations = executableValidator.validateParameters( object, method, parameterValues);assertEquals( 1, violations.size() );Iterator<Node> propertyPath = violations.iterator() .next() .getPropertyPath() .iterator();MethodNode methodNode = propertyPath.next().as( MethodNode.class );assertEquals( "drive", methodNode.getName() );assertEquals( Arrays.<Class<?>>asList( int.class ), methodNode.getParameterTypes() );ParameterNode parameterNode = propertyPath.next().as( ParameterNode.class );assertEquals( "speedInMph", parameterNode.getName() );assertEquals( 0, parameterNode.getParameterIndex() );The parameter name is determined using the currentParameterNameProvider (seeSection 9.2.4, “ParameterNameProvider”).
In addition to the built-in bean and property-level constraints discussed inSection 2.3, “Built-in constraints”, Hibernate Validator currently provides one method-level constraint,@ParameterScriptAssert. This is a generic cross-parameter constraint which allows to implementvalidation routines using any JSR 223 compatible ("Scripting for the JavaTM Platform") scriptinglanguage, provided an engine for this language is available on the classpath.
To refer to the executable’s parameters from within the expression, use their name as obtained fromthe active parameter name provider (seeSection 9.2.4, “ParameterNameProvider”).Example 3.17, “Using@ParameterScriptAssert” shows how the validation logic of the@LuggageCountMatchesPassengerCountconstraint fromExample 3.2, “Declaring a cross-parameter constraint” could be expressed with the help of@ParameterScriptAssert.
@ParameterScriptAssertpackage org.hibernate.validator.referenceguide.chapter03.parameterscriptassert;public class Car { @ParameterScriptAssert(lang = "groovy", script = "luggage.size() <= passengers.size() * 2") public void load(List<Person> passengers, List<PieceOfLuggage> luggage) { //... }}Message interpolation is the process of creating error messages for violated Jakarta Bean Validationconstraints. In this chapter you will learn how such messages are defined and resolved and how youcan plug in custom message interpolators in case the default algorithm is not sufficient for yourrequirements.
Constraint violation messages are retrieved from so called message descriptors. Each constraintdefines its default message descriptor using the message attribute. At declaration time, the defaultdescriptor can be overridden with a specific value as shown inExample 4.1, “Specifying a message descriptor using the message attribute”.
package org.hibernate.validator.referenceguide.chapter04;public class Car { @NotNull(message = "The manufacturer name must not be null") private String manufacturer; //constructor, getters and setters ...}If a constraint is violated, its descriptor will be interpolated by the validation engine using thecurrently configuredMessageInterpolator. The interpolated error message can then be retrieved fromthe resulting constraint violation by callingConstraintViolation#getMessage().
Message descriptors can containmessage parameters as well asmessage expressions which will beresolved during interpolation. Message parameters are string literals enclosed in{}, whilemessage expressions are string literals enclosed in${}. The following algorithm is applied duringmethod interpolation:
Resolve any message parameters by using them as key for the resource bundleValidationMessages. Ifthis bundle contains an entry for a given message parameter, that parameter will be replaced in themessage with the corresponding value from the bundle. This step will be executed recursively in casethe replaced value again contains message parameters. The resource bundle is expected to be providedby the application developer, e.g. by adding a file namedValidationMessages.properties to theclasspath. You can also create localized error messages by providing locale specific variations ofthis bundle, such asValidationMessages_en_US.properties. By default, the JVM’s default locale(Locale#getDefault()) will be used when looking up messages in the bundle.
Resolve any message parameters by using them as key for a resource bundle containing the standarderror messages for the built-in constraints as defined in Appendix B of the Jakarta Bean Validationspecification. In the case of Hibernate Validator, this bundle is namedorg.hibernate.validator.ValidationMessages. If this step triggers a replacement, step 1 is executedagain, otherwise step 3 is applied.
Resolve any message parameters by replacing them with the value of the constraint annotation memberof the same name. This allows to refer to attribute values of the constraint (e.g.Size#min()) inthe error message (e.g. "must be at least ${min}").
Resolve any message expressions by evaluating them as expressions of the Unified ExpressionLanguage. SeeSection 4.1.2, “Interpolation with message expressions” to learn more about the usage ofUnified EL in error messages.
You can find the formal definition of the interpolation algorithm in section6.3.1.1of the Jakarta Bean Validation specification. |
Since the characters{,} and$ have a special meaning in message descriptors, they need to beescaped if you want to use them literally. The following rules apply:
\{ is considered as the literal{
\} is considered as the literal}
\$ is considered as the literal$
\\ is considered as the literal\
As of Hibernate Validator 5 (Bean Validation 1.1) it is possible to use theJakarta Expression Language in constraintviolation messages. This allows to define error messages based on conditional logic and also enablesadvanced formatting options. The validation engine makes the following objects available in the ELcontext:
the attribute values of the constraint mapped to the attribute names
the currently validated value (property, bean, method parameter etc.) under the namevalidatedValue
a bean mapped to the name formatter exposing the var-arg methodformat(String format, Object… args) which behaves likejava.util.Formatter.format(String format, Object… args).
Expression Language is very flexible and Hibernate Validator offers several feature levelsthat you can use to enable Expression Language features through theExpressionLanguageFeatureLevel enum:
NONE: Expression Language interpolation is fully disabled.
VARIABLES: Allow interpolation of the variables injected viaaddExpressionVariable(), resources bundles and usage of theformatter object.
BEAN_PROPERTIES: Allow everythingVARIABLES allows plus the interpolation of bean properties.
BEAN_METHODS: Also allow execution of bean methods. Can be considered safe for hardcoded constraint messages but not forcustom violationswhere extra care is required.
The default feature level for constraint messages isBEAN_PROPERTIES.
You can define the Expression Language feature level whenbootstrapping theValidatorFactory.
The following section provides several examples for using EL expressions in error messages.
Example 4.2, “Specifying message descriptors” shows how to make use of the different options for specifyingmessage descriptors.
package org.hibernate.validator.referenceguide.chapter04.complete;public class Car { @NotNull private String manufacturer; @Size( min = 2, max = 14, message = "The license plate '${validatedValue}' must be between {min} and {max} characters long" ) private String licensePlate; @Min( value = 2, message = "There must be at least {value} seat${value > 1 ? 's' : ''}" ) private int seatCount; @DecimalMax( value = "350", message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher " + "than {value}" ) private double topSpeed; @DecimalMax(value = "100000", message = "Price must not be higher than ${value}") private BigDecimal price; public Car( String manufacturer, String licensePlate, int seatCount, double topSpeed, BigDecimal price) { this.manufacturer = manufacturer; this.licensePlate = licensePlate; this.seatCount = seatCount; this.topSpeed = topSpeed; this.price = price; } //getters and setters ...}Validating an invalidCar instance yields constraint violations with the messages shown by theassertions inExample 4.3, “Expected error messages”:
the@NotNull constraint on themanufacturer field causes the error message "must not be null", asthis is the default message defined by the Jakarta Bean Validation specification and no specific descriptoris given in the message attribute
the@Size constraint on thelicensePlate field shows the interpolation of message parameters({min},{max}) and how to add the validated value to the error message using the ELexpression${validatedValue}
the@Min constraint onseatCount demonstrates how to use an EL expression with a ternary expression todynamically choose singular or plural form, depending on an attribute of the constraint ("There mustbe at least 1 seat" vs. "There must be at least 2 seats")
the message for the@DecimalMax constraint ontopSpeed shows how to format the validatedvalue using the formatter instance
finally, the@DecimalMax constraint onprice shows that parameter interpolation has precedence overexpression evaluation, causing the$ sign to show up in front of the maximum price
Only actual constraint attributes can be interpolated using message parameters in the form |
Car car = new Car( null, "A", 1, 400.123456, BigDecimal.valueOf( 200000 ) );String message = validator.validateProperty( car, "manufacturer" ) .iterator() .next() .getMessage();assertEquals( "must not be null", message );message = validator.validateProperty( car, "licensePlate" ) .iterator() .next() .getMessage();assertEquals( "The license plate 'A' must be between 2 and 14 characters long", message);message = validator.validateProperty( car, "seatCount" ).iterator().next().getMessage();assertEquals( "There must be at least 2 seats", message );message = validator.validateProperty( car, "topSpeed" ).iterator().next().getMessage();assertEquals( "The top speed 400.12 is higher than 350", message );message = validator.validateProperty( car, "price" ).iterator().next().getMessage();assertEquals( "Price must not be higher than $100000", message );If the default message interpolation algorithm does not fit your requirements, it is also possible toplug in a customMessageInterpolator implementation.
Custom interpolators must implement the interfacejavax.validation.MessageInterpolator. Note thatimplementations must be thread-safe. It is recommended that custom message interpolators delegatefinal implementation to the default interpolator, which can be obtained viaConfiguration#getDefaultMessageInterpolator().
In order to use a custom message interpolator it must be registered either by configuring it in theJakarta Bean Validation XML descriptorMETA-INF/validation.xml (seeSection 8.1, “Configuring the validator factory invalidation.xml”) or by passing it when bootstrapping aValidatorFactory orValidator (seeSection 9.2.1, “MessageInterpolator” andSection 9.3, “Configuring a Validator”, respectively).
ResourceBundleLocatorIn some use cases, you want to use the message interpolation algorithm as defined by the BeanValidation specification, but retrieve error messages from other resource bundles thanValidationMessages. In this situation Hibernate Validator’sResourceBundleLocator SPI can help.
The default message interpolator in Hibernate Validator,ResourceBundleMessageInterpolator,delegates retrieval of resource bundles to that SPI. Using an alternative bundle only requirespassing an instance ofPlatformResourceBundleLocator with the bundle name when bootstrapping theValidatorFactory as shown inExample 4.4, “Using a specific resource bundle”.
Validator validator = Validation.byDefaultProvider() .configure() .messageInterpolator( new ResourceBundleMessageInterpolator( new PlatformResourceBundleLocator( "MyMessages" ) ) ) .buildValidatorFactory() .getValidator();Of course you also could implement a completely differentResourceBundleLocator, which for instancereturns bundles backed by records in a database. In this case, you can obtain the default locator viaHibernateValidatorConfiguration#getDefaultResourceBundleLocator(), which you e.g. could use asfall-back for your custom locator.
BesidesPlatformResourceBundleLocator, Hibernate Validator provides another resource bundle locatorimplementation out of the box, namelyAggregateResourceBundleLocator, which allows to retrieve errormessages from more than one resource bundle. You could for instance use this implementation in amulti-module application where you want to have one message bundle per module.Example 4.5, “UsingAggregateResourceBundleLocator” shows how to useAggregateResourceBundleLocator.
AggregateResourceBundleLocatorValidator validator = Validation.byDefaultProvider() .configure() .messageInterpolator( new ResourceBundleMessageInterpolator( new AggregateResourceBundleLocator( Arrays.asList( "MyMessages", "MyOtherMessages" ) ) ) ) .buildValidatorFactory() .getValidator();Note that the bundles are processed in the order as passed to the constructor. That means if severalbundles contain an entry for a given message key, the value will be taken from the first bundle inthe list containing the key.
All validation methods onValidator andExecutableValidator discussed in earlier chapters also takea var-arg argumentgroups. So far we have been ignoring this parameter, but it is time to have acloser look.
Groups allow you to restrict the set of constraints applied during validation. One use case forvalidation groups are UI wizards where in each step only a specified subset of constraints shouldget validated. The groups targeted are passed as var-arg parameters to the appropriate validatemethod.
Let’s have a look at an example. The classPerson inExample 5.1, “Example classPerson” has a@NotNullconstraint onname. Since no group is specified for this annotation the default groupjavax.validation.groups.Default is assumed.
When more than one group is requested, the order in which the groups are evaluated is notdeterministic. If no group is specified the default group |
Personpackage org.hibernate.validator.referenceguide.chapter05;public class Person { @NotNull private String name; public Person(String name) { this.name = name; } // getters and setters ...}The classDriver inExample 5.2, “Driver” extendsPerson and adds the propertiesage andhasDrivingLicense. Drivers must be at least 18 years old (@Min(18)) and have a driving license(@AssertTrue). Both constraints defined on these properties belong to the groupDriverChecks whichis just a simple tagging interface.
Using interfaces makes the usage of groups type-safe and allows for easy refactoring. It also meansthat groups can inherit from each other via class inheritance. SeeSection 5.2, “Group inheritance”. |
package org.hibernate.validator.referenceguide.chapter05;public class Driver extends Person { @Min( value = 18, message = "You have to be 18 to drive a car", groups = DriverChecks.class ) public int age; @AssertTrue( message = "You first have to pass the driving test", groups = DriverChecks.class ) public boolean hasDrivingLicense; public Driver(String name) { super( name ); } public void passedDrivingTest(boolean b) { hasDrivingLicense = b; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}package org.hibernate.validator.referenceguide.chapter05;public interface DriverChecks {}Finally the classCar (Example 5.3, “Car”) has some constraints which are part of the default group aswell as@AssertTrue in the groupCarChecks on the propertypassedVehicleInspection which indicateswhether a car passed the road worthy tests.
package org.hibernate.validator.referenceguide.chapter05;public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; @Min(2) private int seatCount; @AssertTrue( message = "The car has to pass the vehicle inspection first", groups = CarChecks.class ) private boolean passedVehicleInspection; @Valid private Driver driver; public Car(String manufacturer, String licencePlate, int seatCount) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; this.seatCount = seatCount; } public boolean isPassedVehicleInspection() { return passedVehicleInspection; } public void setPassedVehicleInspection(boolean passedVehicleInspection) { this.passedVehicleInspection = passedVehicleInspection; } public Driver getDriver() { return driver; } public void setDriver(Driver driver) { this.driver = driver; } // getters and setters ...}package org.hibernate.validator.referenceguide.chapter05;public interface CarChecks {}Overall three different groups are used in the example:
The constraints onPerson.name,Car.manufacturer,Car.licensePlate andCar.seatCountall belong to theDefault group
The constraints onDriver.age andDriver.hasDrivingLicense belong toDriverChecks
The constraint onCar.passedVehicleInspection belongs to the groupCarChecks
Example 5.4, “Using validation groups” shows how passing different group combinations to theValidator#validate()method results in different validation results.
// create a car and check that everything is ok with it.Car car = new Car( "Morris", "DD-AB-123", 2 );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 0, constraintViolations.size() );// but has it passed the vehicle inspection?constraintViolations = validator.validate( car, CarChecks.class );assertEquals( 1, constraintViolations.size() );assertEquals( "The car has to pass the vehicle inspection first", constraintViolations.iterator().next().getMessage());// let's go to the vehicle inspectioncar.setPassedVehicleInspection( true );assertEquals( 0, validator.validate( car, CarChecks.class ).size() );// now let's add a driver. He is 18, but has not passed the driving test yetDriver john = new Driver( "John Doe" );john.setAge( 18 );car.setDriver( john );constraintViolations = validator.validate( car, DriverChecks.class );assertEquals( 1, constraintViolations.size() );assertEquals( "You first have to pass the driving test", constraintViolations.iterator().next().getMessage());// ok, John passes the testjohn.passedDrivingTest( true );assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );// just checking that everything is in order nowassertEquals( 0, validator.validate( car, Default.class, CarChecks.class, DriverChecks.class).size());The firstvalidate() call inExample 5.4, “Using validation groups” is done using no explicit group. There are novalidation errors, even though the propertypassedVehicleInspection is per defaultfalse asthe constraint defined on this property does not belong to the default group.
The next validation using theCarChecks group fails until the car passes the vehicle inspection.Adding a driver to the car and validating againstDriverChecks again yields one constraint violationdue to the fact that the driver has not yet passed the driving test. Only after settingpassedDrivingTest totrue the validation againstDriverChecks passes.
The lastvalidate() call finally shows that all constraints are passing by validating against alldefined groups.
InExample 5.4, “Using validation groups”, we need to callvalidate() for each validation group, or specify all ofthem one by one.
In some situations, you may want to define a group of constraints which includes another group.You can do that using group inheritance.
InExample 5.5, “SuperCar”, we define aSuperCar and a groupRaceCarChecks that extends theDefault group.ASuperCar must have safety belts to be allowed to run in races.
package org.hibernate.validator.referenceguide.chapter05.groupinheritance;public class SuperCar extends Car { @AssertTrue( message = "Race car must have a safety belt", groups = RaceCarChecks.class ) private boolean safetyBelt; // getters and setters ...}package org.hibernate.validator.referenceguide.chapter05.groupinheritance;import javax.validation.groups.Default;public interface RaceCarChecks extends Default {}In the example below, we will check if aSuperCar with one seat and no security belts is a valid carand if it is a valid race-car.
// create a supercar and check that it's valid as a generic CarSuperCar superCar = new SuperCar( "Morris", "DD-AB-123", 1 );assertEquals( "must be greater than or equal to 2", validator.validate( superCar ).iterator().next().getMessage() );// check that this supercar is valid as generic car and also as race carSet<ConstraintViolation<SuperCar>> constraintViolations = validator.validate( superCar, RaceCarChecks.class );assertThat( constraintViolations ).extracting( "message" ).containsOnly( "Race car must have a safety belt", "must be greater than or equal to 2");On the first call tovalidate(), we do not specify a group. There is one validation error because acar must have at least one seat. It is the constraint from theDefault group.
On the second call, we specify only the groupRaceCarChecks. There are two validation errors: oneabout the missing seat from theDefault group, another one about the fact that there is no safetybelts coming from theRaceCarChecks group.
By default, constraints are evaluated in no particular order, regardless of which groups they belongto. In some situations, however, it is useful to control the order in which constraints are evaluated.
In the example fromExample 5.4, “Using validation groups” it could for instance be required that first all defaultcar constraints are passing before checking the road worthiness of the car. Finally, before drivingaway, the actual driver constraints should be checked.
In order to implement such a validation order you just need to define an interface and annotate itwith@GroupSequence, defining the order in which the groups have to be validated (seeExample 5.7, “Defining a group sequence”). If at least one constraint fails in a sequenced group, none of theconstraints of the following groups in the sequence get validated.
package org.hibernate.validator.referenceguide.chapter05;import javax.validation.GroupSequence;import javax.validation.groups.Default;@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })public interface OrderedChecks {}Groups defining a sequence and groups composing a sequence must not be involved in a cyclicdependency either directly or indirectly, either through cascaded sequence definition or groupinheritance. If a group containing such a circularity is evaluated, a |
You then can use the new sequence as shown in inExample 5.8, “Using a group sequence”.
Car car = new Car( "Morris", "DD-AB-123", 2 );car.setPassedVehicleInspection( true );Driver john = new Driver( "John Doe" );john.setAge( 18 );john.passedDrivingTest( true );car.setDriver( john );assertEquals( 0, validator.validate( car, OrderedChecks.class ).size() );@GroupSequenceBesides defining group sequences, the@GroupSequence annotation also allows to redefine the defaultgroup for a given class. To do so, just add the@GroupSequence annotation to the class and specifythe sequence of groups which substituteDefault for this class within the annotation.
Example 5.9, “ClassRentalCar with redefined default group” introduces a new classRentalCar with a redefined default group.
RentalCar with redefined default grouppackage org.hibernate.validator.referenceguide.chapter05;@GroupSequence({ RentalChecks.class, CarChecks.class, RentalCar.class })public class RentalCar extends Car { @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class) private boolean rented; public RentalCar(String manufacturer, String licencePlate, int seatCount) { super( manufacturer, licencePlate, seatCount ); } public boolean isRented() { return rented; } public void setRented(boolean rented) { this.rented = rented; }}package org.hibernate.validator.referenceguide.chapter05;public interface RentalChecks {}With this definition you can evaluate the constraints belonging toRentalChecks,CarChecks andRentalCar by just requesting theDefault group as seen inExample 5.10, “Validating an object with redefined default group”.
RentalCar rentalCar = new RentalCar( "Morris", "DD-AB-123", 2 );rentalCar.setPassedVehicleInspection( true );rentalCar.setRented( true );Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validate( rentalCar );assertEquals( 1, constraintViolations.size() );assertEquals( "Wrong message", "The car is currently rented out", constraintViolations.iterator().next().getMessage());rentalCar.setRented( false );constraintViolations = validator.validate( rentalCar );assertEquals( 0, constraintViolations.size() );Since there must be no cyclic dependency in the group and group sequence definitions, one cannot justadd |
TheDefault group sequence overriding is local to the class it is defined on and is not propagatedto associated objects. For the example, this means that addingDriverChecks to the default groupsequence ofRentalCar would not have any effects. Only the groupDefault will be propagated to thedriver association.
Note that you can control the propagated group(s) by declaring a group conversion rule (seeSection 5.5, “Group conversion”).
@GroupSequenceProviderIn addition to statically redefining default group sequences via@GroupSequence, Hibernate Validatoralso provides an SPI for the dynamic redefinition of default group sequences depending on the objectstate.
For that purpose, you need to implement the interfaceDefaultGroupSequenceProvider and register thisimplementation with the target class via the@GroupSequenceProvider annotation. In the rental carscenario, you could for instance dynamically add theCarChecks as seen inExample 5.11, “Implementing and using a default group sequence provider”.
package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;public class RentalCarGroupSequenceProvider implements DefaultGroupSequenceProvider<RentalCar> { @Override public List<Class<?>> getValidationGroups(RentalCar car) { List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>(); defaultGroupSequence.add( RentalCar.class ); if ( car != null && !car.isRented() ) { defaultGroupSequence.add( CarChecks.class ); } return defaultGroupSequence; }}package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;@GroupSequenceProvider(RentalCarGroupSequenceProvider.class)public class RentalCar extends Car { @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class) private boolean rented; public RentalCar(String manufacturer, String licencePlate, int seatCount) { super( manufacturer, licencePlate, seatCount ); } public boolean isRented() { return rented; } public void setRented(boolean rented) { this.rented = rented; }}What if you wanted to validate the car related checks together with the driver checks? Of course youcould pass the required groups to the validate call explicitly, but what if you wanted to make thesevalidations occur as part of theDefault group validation? Here@ConvertGroup comes into play whichallows you to use a different group than the originally requested one during cascaded validation.
Let’s have a look atExample 5.12, “@ConvertGroup usage”. Here@GroupSequence({CarChecks.class, Car.class }) is used to combine the car related constraints under theDefault group(seeSection 5.4, “Redefining the default group sequence”). There is also a@ConvertGroup(from = Default.class, to =DriverChecks.class) which ensures theDefault group gets converted to theDriverChecks group duringcascaded validation of the driver association.
@ConvertGroup usagepackage org.hibernate.validator.referenceguide.chapter05.groupconversion;public class Driver { @NotNull private String name; @Min( value = 18, message = "You have to be 18 to drive a car", groups = DriverChecks.class ) public int age; @AssertTrue( message = "You first have to pass the driving test", groups = DriverChecks.class ) public boolean hasDrivingLicense; public Driver(String name) { this.name = name; } public void passedDrivingTest(boolean b) { hasDrivingLicense = b; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } // getters and setters ...}package org.hibernate.validator.referenceguide.chapter05.groupconversion;@GroupSequence({ CarChecks.class, Car.class })public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; @Min(2) private int seatCount; @AssertTrue( message = "The car has to pass the vehicle inspection first", groups = CarChecks.class ) private boolean passedVehicleInspection; @Valid @ConvertGroup(from = Default.class, to = DriverChecks.class) private Driver driver; public Car(String manufacturer, String licencePlate, int seatCount) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; this.seatCount = seatCount; } public boolean isPassedVehicleInspection() { return passedVehicleInspection; } public void setPassedVehicleInspection(boolean passedVehicleInspection) { this.passedVehicleInspection = passedVehicleInspection; } public Driver getDriver() { return driver; } public void setDriver(Driver driver) { this.driver = driver; } // getters and setters ...}As a result the validation inExample 5.13, “Test case for@ConvertGroup” succeeds, even though the constraintonhasDrivingLicense belongs to theDriverChecks group and only theDefault group is requested inthevalidate() call.
@ConvertGroup// create a car and validate. The Driver is still null and does not get validatedCar car = new Car( "VW", "USD-123", 4 );car.setPassedVehicleInspection( true );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 0, constraintViolations.size() );// create a driver who has not passed the driving testDriver john = new Driver( "John Doe" );john.setAge( 18 );// now let's add a driver to the carcar.setDriver( john );constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );assertEquals( "The driver constraint should also be validated as part of the default group", constraintViolations.iterator().next().getMessage(), "You first have to pass the driving test");You can define group conversions wherever@Valid can be used, namely associations as well as methodand constructor parameters and return values. Multiple conversions can be specified using@ConvertGroup.List.
However, the following restrictions apply:
@ConvertGroup must only be used in combination with@Valid. If used without, aConstraintDeclarationException is thrown.
It is not legal to have multiple conversion rules on the same element with the same from value.In this case, aConstraintDeclarationException is raised.
Thefrom attribute must not refer to a group sequence. AConstraintDeclarationException israised in this situation.
Rules are not executed recursively. The first matching conversion rule is used and subsequent rulesare ignored. For example if a set of |
The Jakarta Bean Validation API defines a whole set of standard constraint annotations such as@NotNull,@Size etc. In cases where these built-in constraints are not sufficient, you can easily createcustom constraints tailored to your specific validation requirements.
To create a custom constraint, the following three steps are required:
Create a constraint annotation
Implement a validator
Define a default error message
This section shows how to write a constraint annotation which can be used to ensure that a givenstring is either completely upper case or lower case. Later on, this constraint will be applied tothelicensePlate field of theCar class fromChapter 1,Getting started to ensure thatthe field is always an upper-case string.
The first thing needed is a way to express the two case modes. While you could useString constants,a better approach is using an enum for that purpose:
CaseMode to express upper vs. lower casepackage org.hibernate.validator.referenceguide.chapter06;public enum CaseMode { UPPER, LOWER;}The next step is to define the actual constraint annotation. If you’ve never designed an annotationbefore, this may look a bit scary, but actually it’s not that hard:
@CheckCase constraint annotationpackage org.hibernate.validator.referenceguide.chapter06;import static java.lang.annotation.ElementType.ANNOTATION_TYPE;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.ElementType.METHOD;import static java.lang.annotation.ElementType.PARAMETER;import static java.lang.annotation.ElementType.TYPE_USE;import static java.lang.annotation.RetentionPolicy.RUNTIME;@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })@Retention(RUNTIME)@Constraint(validatedBy = CheckCaseValidator.class)@Documented@Repeatable(List.class)public @interface CheckCase { String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." + "message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; CaseMode value(); @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE }) @Retention(RUNTIME) @Documented @interface List { CheckCase[] value(); }}An annotation type is defined using the@interface keyword. All attributes of an annotation type aredeclared in a method-like manner. The specification of the Jakarta Bean Validation API demands, that anyconstraint annotation defines:
an attributemessage that returns the default key for creating error messages in case theconstraint is violated
an attributegroups that allows the specification of validation groups, to which this constraintbelongs (seeChapter 5,Grouping constraints). This must default to an empty array of type Class<?>.
an attributepayload that can be used by clients of the Jakarta Bean Validation API to assign custompayload objects to a constraint. This attribute is not used by the API itself. An example for acustom payload could be the definition of a severity:
public class Severity { public interface Info extends Payload { } public interface Error extends Payload { }}public class ContactDetails { @NotNull(message = "Name is mandatory", payload = Severity.Error.class) private String name; @NotNull(message = "Phone number not specified, but not mandatory", payload = Severity.Info.class) private String phoneNumber; // ...}Now a client can after the validation of aContactDetails instance access the severity of aconstraint usingConstraintViolation.getConstraintDescriptor().getPayload() and adjust its behaviordepending on the severity.
Besides these three mandatory attributes there is another one,value, allowing for the required casemode to be specified. The namevalue is a special one, which can be omitted when using theannotation, if it is the only attribute specified, as e.g. in@CheckCase(CaseMode.UPPER).
In addition, the constraint annotation is decorated with a couple of meta annotations:
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}): Defines the supported targetelement types for the constraint.@CheckCase may be used on fields (element typeFIELD), JavaBeans propertiesas well as method return values (METHOD), method/constructor parameters (PARAMETER) and type argument ofparameterized types (TYPE_USE). The element typeANNOTATION_TYPE allows for the creation of composedconstraints (seeSection 6.4, “Constraint composition”) based on@CheckCase.
When creating a class-level constraint (seeSection 2.1.4, “Class-level constraints”), the elementtypeTYPE would have to be used. Constraints targeting the return value of a constructor need tosupport the element typeCONSTRUCTOR. Cross-parameter constraints (seeSection 6.3, “Cross-parameter constraints”) which are used to validate all the parameters of a methodor constructor together, must supportMETHOD orCONSTRUCTOR, respectively.
@Retention(RUNTIME): Specifies, that annotations of this type will be available at runtime by themeans of reflection
@Constraint(validatedBy = CheckCaseValidator.class): Marks the annotation type as constraintannotation and specifies the validator to be used to validate elements annotated with@CheckCase.If a constraint may be used on several data types, several validators may be specified, one foreach data type.
@Documented: Says, that the use of@CheckCase will be contained in the JavaDoc of elementsannotated with it
@Repeatable(List.class): Indicates that the annotation can be repeated several times at thesame place, usually with a different configuration.List is the containing annotation type.
This containing annotation type namedList is also shown in the example. It allows to specify several@CheckCase annotations on the same element, e.g. with different validation groups and messages.While another name could be used, the Jakarta Bean Validation specification recommends to use the nameList and make the annotation an inner annotation of the corresponding constraint type.
Having defined the annotation, you need to create a constraint validator, which is able to validateelements with a@CheckCase annotation. To do so, implement the Jakarta Bean Validation interfaceConstraintValidatoras shown below:
@CheckCasepackage org.hibernate.validator.referenceguide.chapter06;public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; @Override public void initialize(CheckCase constraintAnnotation) { this.caseMode = constraintAnnotation.value(); } @Override public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if ( object == null ) { return true; } if ( caseMode == CaseMode.UPPER ) { return object.equals( object.toUpperCase() ); } else { return object.equals( object.toLowerCase() ); } }}TheConstraintValidator interface defines two type parameters which are set in the implementation.The first one specifies the annotation type to be validated (CheckCase), the second one the type ofelements, which the validator can handle (String). In case a constraint supports several data types,aConstraintValidator for each allowed type has to be implemented and registered at the constraintannotation as shown above.
The implementation of the validator is straightforward. Theinitialize() method gives you access tothe attribute values of the validated constraint and allows you to store them in a field of thevalidator as shown in the example.
TheisValid() method contains the actual validation logic. For@CheckCase this is the check whethera given string is either completely lower case or upper case, depending on the case mode retrievedininitialize(). Note that the Jakarta Bean Validation specification recommends to consider null values asbeing valid. Ifnull is not a valid value for an element, it should be annotated with@NotNullexplicitly.
ConstraintValidatorContextExample 6.3, “Implementing a constraint validator for the constraint@CheckCase”relies on the default error message generation by just returningtrue orfalse from theisValid()method. Using the passedConstraintValidatorContext object, it is possible to either add additionalerror messages or completely disable the default error message generation and solely define customerror messages. TheConstraintValidatorContext API is modeled as fluent interface and is bestdemonstrated with an example:
ConstraintValidatorContext to define custom error messagespackage org.hibernate.validator.referenceguide.chapter06.constraintvalidatorcontext;public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; @Override public void initialize(CheckCase constraintAnnotation) { this.caseMode = constraintAnnotation.value(); } @Override public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if ( object == null ) { return true; } boolean isValid; if ( caseMode == CaseMode.UPPER ) { isValid = object.equals( object.toUpperCase() ); } else { isValid = object.equals( object.toLowerCase() ); } if ( !isValid ) { constraintContext.disableDefaultConstraintViolation(); constraintContext.buildConstraintViolationWithTemplate( "{org.hibernate.validator.referenceguide.chapter06." + "constraintvalidatorcontext.CheckCase.message}" ) .addConstraintViolation(); } return isValid; }}Example 6.4, “UsingConstraintValidatorContext to define custom error messages”shows how you can disable the default error message generation and add a custom error message usinga specified message template. In this example the use of theConstraintValidatorContext results inthe same error message as the default error message generation.
It is important to add each configured constraint violation by calling |
By default, Expression Language is not enabled for custom violations created in theConstraintValidatorContext.
However, for some advanced requirements, using Expression Language might be necessary.
In this case, you need to unwrap theHibernateConstraintValidatorContext and enable Expression Language explicitly.SeeSection 12.13.1, “HibernateConstraintValidatorContext” for more information.
Refer toSection 6.2.1, “Custom property paths” to learn how to use theConstraintValidatorContext API tocontrol the property path of constraint violations for class-level constraints.
HibernateConstraintValidator extensionHibernate Validator provides an extension to theConstraintValidator contract:HibernateConstraintValidator.
The purpose of this extension is to provide more contextual information to theinitialize() methodas, in the currentConstraintValidator contract, only the annotation is passed as parameter.
Theinitialize() method ofHibernateConstraintValidator takes two parameters:
TheConstraintDescriptor of the constraint at hand.You can get access to the annotation usingConstraintDescriptor#getAnnotation().
TheHibernateConstraintValidatorInitializationContext which provides useful helpers and contextualinformation, such as the clock provider or the temporal validation tolerance.
This extension is marked as incubating so it might be subject to change.The plan is to standardize it and to include it in Jakarta Bean Validation in the future.
The example below shows how to base your validators onHibernateConstraintValidator:
HibernateConstraintValidator contractpackage org.hibernate.validator.referenceguide.chapter06;public class MyFutureValidator implements HibernateConstraintValidator<MyFuture, Instant> { private Clock clock; private boolean orPresent; @Override public void initialize(ConstraintDescriptor<MyFuture> constraintDescriptor, HibernateConstraintValidatorInitializationContext initializationContext) { this.orPresent = constraintDescriptor.getAnnotation().orPresent(); this.clock = initializationContext.getClockProvider().getClock(); } @Override public boolean isValid(Instant instant, ConstraintValidatorContext constraintContext) { //... return false; }}You should only implement one of the |
From time to time, you might want to condition the constraint validator behavior on some external parameters.
For instance, your zip code validator could vary depending on the locale of your application instance if you have oneinstance per country.Another requirement could be to have different behaviors on specific environments: the staging environment may not haveaccess to some external production resources necessary for the correct functioning of a validator.
The notion of constraint validator payload was introduced for all these use cases.It is an object passed from theValidator instance to each constraint validator via theHibernateConstraintValidatorContext.
The example below shows how to set a constraint validator payload during theValidatorFactory initialization.Unless you override this default value, all theValidators created by thisValidatorFactory will have thisconstraint validator payload value set.
ValidatorFactory initializationValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .constraintValidatorPayload( "US" ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Another option is to set the constraint validator payload perValidator using a context:
Validator contextHibernateValidatorFactory hibernateValidatorFactory = Validation.byDefaultProvider() .configure() .buildValidatorFactory() .unwrap( HibernateValidatorFactory.class );Validator validator = hibernateValidatorFactory.usingContext() .constraintValidatorPayload( "US" ) .getValidator();// [...] US specific validation checksvalidator = hibernateValidatorFactory.usingContext() .constraintValidatorPayload( "FR" ) .getValidator();// [...] France specific validation checksOnce you have set the constraint validator payload, it can be used in your constraint validators as shown in the example below:
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorpayload;public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> { public String countryCode; @Override public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if ( object == null ) { return true; } boolean isValid = false; String countryCode = constraintContext .unwrap( HibernateConstraintValidatorContext.class ) .getConstraintValidatorPayload( String.class ); if ( "US".equals( countryCode ) ) { // checks specific to the United States } else if ( "FR".equals( countryCode ) ) { // checks specific to France } else { // ... } return isValid; }}HibernateConstraintValidatorContext#getConstraintValidatorPayload() has a type parameterand returns the payload only if the payload is of the given type.
It is important to note that the constraint validator payload is different from the dynamic payload you can include inthe constraint violation raised. The whole purpose of this constraint validator payload is to be used to condition the behavior of your constraint validators.It is not included in the constraint violations, unless a specific |
The last missing building block is an error message which should be used in case a@CheckCaseconstraint is violated. To define this, create a fileValidationMessages.properties with thefollowing contents (see alsoSection 4.1, “Default message interpolation”):
CheckCase constraintorg.hibernate.validator.referenceguide.chapter06.CheckCase.message=Case mode must be {value}.If a validation error occurs, the validation runtime will use the default value, that you specifiedfor the message attribute of the@CheckCase annotation to look up the error message in this resourcebundle.
You can now use the constraint in theCar class from theChapter 1,Getting started chapter tospecify that thelicensePlate field should only contain upper-case strings:
@CheckCase constraintpackage org.hibernate.validator.referenceguide.chapter06;public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) @CheckCase(CaseMode.UPPER) private String licensePlate; @Min(2) private int seatCount; public Car(String manufacturer, String licencePlate, int seatCount) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; this.seatCount = seatCount; } //getters and setters ...}Finally,Example 6.11, “Validating objects with the@CheckCase constraint” demonstrates how validating aCar instance with an invalidlicense plate causes the@CheckCase constraint to be violated.
@CheckCase constraint//invalid license plateCar car = new Car( "Morris", "dd-ab-123", 4 );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );assertEquals( "Case mode must be UPPER.", constraintViolations.iterator().next().getMessage());//valid license platecar = new Car( "Morris", "DD-AB-123", 4 );constraintViolations = validator.validate( car );assertEquals( 0, constraintViolations.size() );As discussed earlier, constraints can also be applied on the class level to validate the state of anentire object. Class-level constraints are defined in the same way as are property constraints.Example 6.12, “Implementing a class-level constraint” shows constraint annotation and validator of the@ValidPassengerCount constraint you already saw in use inExample 2.9, “Class-level constraint”.
package org.hibernate.validator.referenceguide.chapter06.classlevel;@Target({ TYPE, ANNOTATION_TYPE })@Retention(RUNTIME)@Constraint(validatedBy = { ValidPassengerCountValidator.class })@Documentedpublic @interface ValidPassengerCount { String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." + "ValidPassengerCount.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };}package org.hibernate.validator.referenceguide.chapter06.classlevel;public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> { @Override public void initialize(ValidPassengerCount constraintAnnotation) { } @Override public boolean isValid(Car car, ConstraintValidatorContext context) { if ( car == null ) { return true; } return car.getPassengers().size() <= car.getSeatCount(); }}As the example demonstrates, you need to use the element typeTYPE in the@Target annotation. Thisallows the constraint to be put on type definitions. The validator of the constraint in the examplereceives aCar in theisValid() method and can access the complete object state to decide whetherthe given instance is valid or not.
By default the constraint violation for a class-level constraint is reported on the level of theannotated type, e.g.Car.
In some cases it is preferable though that the violation’s property path refers to one of theinvolved properties. For instance you might want to report the@ValidPassengerCount constraintagainst the passengers property instead of theCar bean.
Example 6.13, “Adding a newConstraintViolation with custom property path”shows how this can be done by using the constraint validator context passed toisValid() to build acustom constraint violation with a property node for the property passengers. Note that you alsocould add several property nodes, pointing to a sub-entity of the validated bean.
ConstraintViolation with custom property pathpackage org.hibernate.validator.referenceguide.chapter06.custompath;public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> { @Override public void initialize(ValidPassengerCount constraintAnnotation) { } @Override public boolean isValid(Car car, ConstraintValidatorContext constraintValidatorContext) { if ( car == null ) { return true; } boolean isValid = car.getPassengers().size() <= car.getSeatCount(); if ( !isValid ) { constraintValidatorContext.disableDefaultConstraintViolation(); constraintValidatorContext .buildConstraintViolationWithTemplate( "{my.custom.template}" ) .addPropertyNode( "passengers" ).addConstraintViolation(); } return isValid; }}Jakarta Bean Validation distinguishes between two different kinds of constraints.
Generic constraints (which have been discussed so far) apply to the annotated element, e.g. a type,field, container element, method parameter or return value etc.Cross-parameter constraints, in contrast, apply to the array of parameters of a method or constructorand can be used to express validation logic which depends on several parameter values.
In order to define a cross-parameter constraint, its validator class must be annotated with@SupportedValidationTarget(ValidationTarget.PARAMETERS). The type parameterT from theConstraintValidator interface must resolve to eitherObject orObject[] in order to receive thearray of method/constructor arguments in theisValid() method.
The following example shows the definition of a cross-parameter constraint which can be used tocheck that twoDate parameters of a method are in the correct order:
package org.hibernate.validator.referenceguide.chapter06.crossparameter;@Constraint(validatedBy = ConsistentDateParametersValidator.class)@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })@Retention(RUNTIME)@Documentedpublic @interface ConsistentDateParameters { String message() default "{org.hibernate.validator.referenceguide.chapter04." + "crossparameter.ConsistentDateParameters.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };}The definition of a cross-parameter constraint isn’t any different from defining a genericconstraint, i.e. it must specify the membersmessage(),groups() andpayload() and be annotated with@Constraint. This meta annotation also specifies the corresponding validator, which is shown inExample 6.15, “Generic and cross-parameter constraint”. Note that besides the element typesMETHOD andCONSTRUCTORalsoANNOTATION_TYPE is specified as target of the annotation, in order to enable the creation ofcomposed constraints based on@ConsistentDateParameters (seeSection 6.4, “Constraint composition”).
Cross-parameter constraints are specified directly on the declaration of a method or constructor,which is also the case for return value constraints. In order to improve code readability, it istherefore recommended to choose constraint names - such as |
package org.hibernate.validator.referenceguide.chapter06.crossparameter;@SupportedValidationTarget(ValidationTarget.PARAMETERS)public class ConsistentDateParametersValidator implements ConstraintValidator<ConsistentDateParameters, Object[]> { @Override public void initialize(ConsistentDateParameters constraintAnnotation) { } @Override public boolean isValid(Object[] value, ConstraintValidatorContext context) { if ( value.length != 2 ) { throw new IllegalArgumentException( "Illegal method signature" ); } //leave null-checking to @NotNull on individual parameters if ( value[0] == null || value[1] == null ) { return true; } if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) { throw new IllegalArgumentException( "Illegal method signature, expected two " + "parameters of type Date." ); } return ( (Date) value[0] ).before( (Date) value[1] ); }}As discussed above, the validation targetPARAMETERS must be configured for a cross-parametervalidator by using the@SupportedValidationTarget annotation. Since a cross-parameter constraintcould be applied to any method or constructor, it is considered a best practice to check for theexpected number and types of parameters in the validator implementation.
As with generic constraints,null parameters should be considered valid and@NotNull on theindividual parameters should be used to make sure that parameters are notnull.
Similar to class-level constraints, you can create custom constraint violations on single parametersinstead of all parameters when validating a cross-parameter constraint. Just obtain a node builderfrom the |
In rare situations a constraint is both, generic and cross-parameter. This is the case if aconstraint has a validator class which is annotated with@SupportedValidationTarget({ValidationTarget.PARAMETERS, ValidationTarget.ANNOTATED_ELEMENT}) or ifit has a generic and a cross-parameter validator class.
When declaring such a constraint on a method which has parameters and also a return value, theintended constraint target can’t be determined. Constraints which are generic and cross-parameter atthe same time must therefore define a membervalidationAppliesTo() which allows the constraint userto specify the constraint’s target as shown inExample 6.16, “Generic and cross-parameter constraint”.
package org.hibernate.validator.referenceguide.chapter06.crossparameter;@Constraint(validatedBy = { ScriptAssertObjectValidator.class, ScriptAssertParametersValidator.class})@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })@Retention(RUNTIME)@Documentedpublic @interface ScriptAssert { String message() default "{org.hibernate.validator.referenceguide.chapter04." + "crossparameter.ScriptAssert.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; String script(); ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;}The@ScriptAssert constraint has two validators (not shown), a generic and a cross-parameter one andthus defines the membervalidationAppliesTo(). The default valueIMPLICIT allows to derive thetarget automatically in situations where this is possible (e.g. if the constraint is declared on afield or on a method which has parameters but no return value).
If the target can not be determined implicitly, it must be set by the user to eitherPARAMETERS orRETURN_VALUE as shown inExample 6.17, “Specifying the target for a generic and cross-parameter constraint”.
@ScriptAssert(script = "arg1.size() <= arg0", validationAppliesTo = ConstraintTarget.PARAMETERS)public Car buildCar(int seatCount, List<Passenger> passengers) { //... return null;}Looking at thelicensePlate field of theCar class inExample 6.10, “Applying the@CheckCase constraint”, you see threeconstraint annotations already. In more complex scenarios, where even more constraints could be appliedto one element, this might easily become a bit confusing. Furthermore, if there was alicensePlatefield in another class, you would have to copy all constraint declarations to the other class aswell, violating the DRY principle.
You can address this kind of problem by creating higher level constraints, composed from severalbasic constraints.Example 6.18, “Creating a composing constraint@ValidLicensePlate” shows a composed constraint annotation whichcomprises the constraints@NotNull,@Size and@CheckCase:
@ValidLicensePlatepackage org.hibernate.validator.referenceguide.chapter06.constraintcomposition;@NotNull@Size(min = 2, max = 14)@CheckCase(CaseMode.UPPER)@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })@Retention(RUNTIME)@Constraint(validatedBy = { })@Documentedpublic @interface ValidLicensePlate { String message() default "{org.hibernate.validator.referenceguide.chapter06." + "constraintcomposition.ValidLicensePlate.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };}To create a composed constraint, simply annotate the constraint declaration with its comprisingconstraints. If the composed constraint itself requires a validator, this validator is to bespecified within the@Constraint annotation. For composed constraints which don’t need an additionalvalidator such as@ValidLicensePlate, just setvalidatedBy() to an empty array.
Using the new composed constraint at thelicensePlate field is fully equivalent to the previousversion, where the three constraints were declared directly at the field itself:
ValidLicensePlatepackage org.hibernate.validator.referenceguide.chapter06.constraintcomposition;public class Car { @ValidLicensePlate private String licensePlate; //...}The set ofConstraintViolations retrieved when validating aCar instance will contain an entry foreach violated composing constraint of the@ValidLicensePlate constraint. If you rather prefer asingleConstraintViolation in case any of the composing constraints is violated, the@ReportAsSingleViolation meta constraint can be used as follows:
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition.reportassingle;//...@ReportAsSingleViolationpublic @interface ValidLicensePlate { String message() default "{org.hibernate.validator.referenceguide.chapter06." + "constraintcomposition.reportassingle.ValidLicensePlate.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };}Value extraction is the process of extracting values from a container so thatthey can be validated.
It is used when dealing withcontainer elementconstraints andcascaded validation insidecontainers.
Hibernate Validator comes with built-in value extractors for the usual Java containertypes so, except if you are using your own custom container types (or the onesof external libraries such asGuava'sMultimap),you should not have to add your own value extractors.
Built-in value extractors are present for all the following container types:
java.util.Iterable;
java.util.List;
java.util.Map: for keys and values;
java.util.Optional,java.util.OptionalInt,java.util.OptionalLong andjava.util.OptionalDouble;
JavaFX'sObservableValue (seeSection 7.4, “JavaFX value extractors”for more details).
The complete list of built-in value extractors with all the details on how theybehave can be found in theJakarta Bean Validation specification.
ValueExtractorTo extract values from a custom container, one needs to implement aValueExtractor.
Implementing a |
ValueExtractor is a very simple API as the only purpose of a value extractor is to providethe extracted values to aValueReceiver.
For instance, let’s consider the case of Guava’sOptional. It is an easy exampleas we can shape its value extractor after thejava.util.Optional one:
ValueExtractor for Guava’sOptionalpackage org.hibernate.validator.referenceguide.chapter07.valueextractor;public class OptionalValueExtractor implements ValueExtractor<Optional<@ExtractedValue ?>> { @Override public void extractValues(Optional<?> originalValue, ValueReceiver receiver) { receiver.value( null, originalValue.orNull() ); }}Some explanations are in order:
The@ExtractedValue annotation marks the type argument under consideration: itis going to be used to resolve the type of the validated value;
We use thevalue() method of the receiver asOptional is a pure wrapper type;
We don’t want to add a node to the property path of the constraint violationas we want the violation to be reported as if it were directly on the propertyso we pass anull node name tovalue().
A more interesting example is the case of Guava’sMultimap: we would like to beable to validate both the keys and the values of this container type.
Let’s first consider the case of the values. A value extractor extracting themis required:
ValueExtractor forMultimap valuespackage org.hibernate.validator.referenceguide.chapter07.valueextractor;public class MultimapValueValueExtractor implements ValueExtractor<Multimap<?, @ExtractedValue ?>> { @Override public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) { for ( Entry<?, ?> entry : originalValue.entries() ) { receiver.keyedValue( "<multimap value>", entry.getKey(), entry.getValue() ); } }}It allows to validate constraints for the values of theMultimap:
Multimapprivate Multimap<String, @NotBlank String> map1;Another value extractor is required to be able to put constraints on the keysof aMultimap:
ValueExtractor forMultimap keyspackage org.hibernate.validator.referenceguide.chapter07.valueextractor;public class MultimapKeyValueExtractor implements ValueExtractor<Multimap<@ExtractedValue ?, ?>> { @Override public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) { for ( Object key : originalValue.keySet() ) { receiver.keyedValue( "<multimap key>", key, key ); } }}Once these two value extractors are registered, you can declare constraints on thekeys and values of aMultimap:
Multimapprivate Multimap<@NotBlank String, @NotBlank String> map2;The differences between the two value extractors may be a bit subtle at a firstglance so let’s shed some light on them:
The@ExtractedValue annotation marks the targeted type argument (eitherK orV in this case).
We use different node names (<multimap key> vs.<multimap value>).
In one case, we pass the values to the receiver (third argument of thekeyedValue() call), in the other, we pass the keys.
Depending on your container type, you should choose theValueReceivermethod fitting the best:
value()for a simple wrapping container - it is used forOptionals
iterableValue()for an iterable container - it is used forSets
indexedValue()for a container containing indexed values - it is used forLists
keyedValue()for a container containing keyed values - it is used forMaps.It is used for both the keys and the values. In the case of keys,the key is also passed as the validated value.
For all these methods, you need to pass a node name: it is the name included inthe node added to the property path of the constraint violation. As mentionedearlier, if the node name isnull, no node is added to the property path:it is be useful for pure wrapper types similar toOptional.
The choice of the method used is important as it adds contextual information to theproperty path of the constraint violation e.g. the index or the key of thevalidated value.
You might have noticed that, until now, we only implemented value extractorsfor generic containers.
Hibernate Validator also supports value extraction for non generic containers.
Let’s take the case ofjava.util.OptionalInt which wraps a primitiveintinto anOptional-like container.
A first attempt at a value extractor forOptionalInt would look like:
ValueExtractor forOptionalIntpackage org.hibernate.validator.referenceguide.chapter07.nongeneric;public class OptionalIntValueExtractor implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> { @Override public void extractValues(OptionalInt originalValue, ValueReceiver receiver) { receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null ); }}There is an obvious thing missing for a non generic container: we don’t havea type parameter. It has two consequences:
we cannot determine the type of the validated value using the type argument;
we cannot add constraints on the type argument (e.g.Container<@NotNull String>).
First things first, we need a way to tell Hibernate Validator that the valueextracted from anOptionalInt is of typeInteger.As you can see in the above example, thetype attribute of the@ExtractedValueannotation allows to provide this information to the validation engine.
Then you have to tell the validation engine that theMin constraint you want toadd to theOptionalInt property relates to the wrapped value and not the wrapper.
Jakarta Bean Validation provides theUnwrapping.Unwrap payload for this situation:
Unwrapping.Unwrap payload@Min(value = 5, payload = Unwrapping.Unwrap.class)private OptionalInt optionalInt1;If we take a step back, most - if not all - the constraints we would like to add to anOptionalInt property would be applied to the wrapped value so having a way to make itthe default would be nice.
This is exactly what the@UnwrapByDefault annotation is for:
ValueExtractor forOptionalInt marked with@UnwrapByDefaultpackage org.hibernate.validator.referenceguide.chapter07.nongeneric;@UnwrapByDefaultpublic class UnwrapByDefaultOptionalIntValueExtractor implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> { @Override public void extractValues(OptionalInt originalValue, ValueReceiver receiver) { receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null ); }}When declaring this value extractor forOptionalInt, constraint annotations willby default be applied to the wrapped value:
@UnwrapByDefault@Min(5)private OptionalInt optionalInt2;Note that you can still declare an annotation for the wrapper itself by usingtheUnwrapping.Skip payload:
Unwrapping.Skip@NotNull(payload = Unwrapping.Skip.class)@Min(5)private OptionalInt optionalInt3;The |
Bean properties in JavaFX are typically not of simple data types likeStringorint, but are wrapped inProperty types which allows to make them observable,use them for data binding etc.
Thus, value extraction is required to be able to apply constraints on thewrapped values.
The JavaFXObservableValue value extractor is marked with@UnwrapByDefault.As such, the constraints hosted on the container target the wrapped value bydefault.
Thus, you can constrain aStringProperty as below:
StringProperty@NotBlankprivate StringProperty stringProperty;Or aLongProperty:
LongProperty@Min(5)private LongProperty longProperty;The iterable property types, namelyReadOnlyListProperty,ListProperty and theirSet andMap counterparts are generic and, as such,container element constraints can be used. Thus, they have specific valueextractors that are not marked with@UnwrapByDefault.
AReadOnlyListProperty would naturally be constrained as aList:
ReadOnlyListProperty@Size(min = 1)private ReadOnlyListProperty<@NotBlank String> listProperty;ValueExtractorHibernate Validator does not detect automatically the value extractors in theclasspath so they have to be registered.
There are several ways to register value extractors (in increasing order ofpriority):
The fileMETA-INF/services/javax.validation.valueextraction.ValueExtractormust be provided, with the fully-qualified names of one or more valueextractor implementations as its contents, each on a separate line.
META-INF/validation.xml fileSeeSection 8.1, “Configuring the validator factory invalidation.xml” for more information abouthow to register value extractors in the XML configuration.
Configuration#addValueExtractor(ValueExtractor<?>)SeeSection 9.2.6, “RegisteringValueExtractors”for more information.
ValidatorContext#addValueExtractor(ValueExtractor<?>)It only declares the value extractor for thisValidator instance.
A value extractor for a given type and type parameter specified at a higherpriority overrides any other extractors for the same type and type parametergiven at lower priorities.
In most cases, you should not have to worry about this but, if you are overridingexisting value extractors, you can find a detailed description of the valueextractors resolution algorithms in the Jakarta Bean Validation specification:
One important thing to have in mind is that:
for container element constraints, the declared type is used to resolve the valueextractors;
for cascaded validation, it is the runtime type.
So far we have used the default configuration source for Jakarta Bean Validation, namely annotations.However, there also exist two kinds of XML descriptors allowing configuration via XML. The firstdescriptor describes general Jakarta Bean Validation behaviour and is provided asMETA-INF/validation.xml.The second one describes constraint declarations and closely matches the constraint declarationapproach via annotations. Let’s have a look at these two document types.
The XSD files are available viahttp://xmlns.jcp.org/xml/ns/validation/configuration andhttp://xmlns.jcp.org/xml/ns/validation/mapping. More information about the XML schemas can be found on theJakarta Bean Validation website. |
The key to enable XML configuration for Hibernate Validator is the fileMETA-INF/validation.xml.If this file exists on the classpath its configuration will be applied when theValidatorFactorygets created.Figure 1, “Validation configuration schema” shows a model view of the XML schema to whichvalidation.xml has to adhere.

Example 8.1, “validation.xml”shows the several configuration options ofvalidation.xml. All settings are optional and the sameconfiguration options are also available programmatically throughjavax.validation.Configuration. Infact, the XML configuration will be overridden by values explicitly specified via the programmaticAPI. It is even possible to ignore the XML configuration completely viaConfiguration#ignoreXmlConfiguration(). See alsoSection 9.2, “Configuring aValidatorFactory”.
validation.xml<validation-config xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd" version="2.0"> <default-provider>com.acme.ValidationProvider</default-provider> <message-interpolator>com.acme.MessageInterpolator</message-interpolator> <traversable-resolver>com.acme.TraversableResolver</traversable-resolver> <constraint-validator-factory> com.acme.ConstraintValidatorFactory </constraint-validator-factory> <parameter-name-provider>com.acme.ParameterNameProvider</parameter-name-provider> <clock-provider>com.acme.ClockProvider</clock-provider> <value-extractor>com.acme.ContainerValueExtractor</value-extractor> <executable-validation enabled="true"> <default-validated-executable-types> <executable-type>CONSTRUCTORS</executable-type> <executable-type>NON_GETTER_METHODS</executable-type> <executable-type>GETTER_METHODS</executable-type> </default-validated-executable-types> </executable-validation> <constraint-mapping>META-INF/validation/constraints-car.xml</constraint-mapping> <property name="hibernate.validator.fail_fast">false</property></validation-config>There must only be one file namedMETA-INF/validation.xml on the classpath. If more than one isfound an exception is thrown. |
The nodedefault-provider allows to choose the Jakarta Bean Validation provider. This is useful if there ismore than one provider on the classpath.message-interpolator,traversable-resolver,constraint-validator-factory,parameter-name-provider andclock-provider allow to customizethe used implementations for the interfacesMessageInterpolator,TraversableResolver,ConstraintValidatorFactory,ParameterNameProvider andClockProvider defined in thejavax.validation package.See the sub-sections ofSection 9.2, “Configuring aValidatorFactory” for more information about theseinterfaces.
value-extractor allows to declare additional value extractors either to extract values from customcontainer types or to override the built-in value extractors. SeeChapter 7,Value extraction formore information about how to implementjavax.validation.valueextraction.ValueExtractor.
executable-validation and its subnodes define defaults for method validation. The Jakarta Bean Validationspecification defines constructor and non getter methods as defaults. The enabled attribute acts asglobal switch to turn method validation on and off (see alsoChapter 3,Declaring and validating method constraints).
Via theconstraint-mapping element you can list an arbitrary number of additional XML filescontaining the actual constraint configuration. Mapping file names must be specified using theirfully-qualified name on the classpath. Details on writing mapping files can be found in the nextsection.
Last but not least, you can specify provider specific properties via theproperty nodes. In theexample, we are using the Hibernate Validator specifichibernate.validator.fail_fast property (seeSection 12.2, “Fail fast mode”).
constraint-mappingsExpressing constraints in XML is possible via files adhering to the schema seen inFigure 2, “Validation mapping schema”. Note that these mapping files are only processed if listed viaconstraint-mapping invalidation.xml.

Example 8.2, “Bean constraints configured via XML” shows how the classes Car and RentalCar fromExample 5.3, “Car” resp.Example 5.9, “ClassRentalCar with redefined default group” could be mapped in XML.
<constraint-mappings xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd" version="2.0"> <default-package>org.hibernate.validator.referenceguide.chapter05</default-package> <bean ignore-annotations="true"> <field name="manufacturer"> <constraint annotation="javax.validation.constraints.NotNull"/> </field> <field name="licensePlate"> <constraint annotation="javax.validation.constraints.NotNull"/> </field> <field name="seatCount"> <constraint annotation="javax.validation.constraints.Min"> <element name="value">2</element> </constraint> </field> <field name="driver"> <valid/> </field> <field name="partManufacturers"> <container-element-type type-argument-index="0"> <valid/> </container-element-type> <container-element-type type-argument-index="1"> <container-element-type> <valid/> <constraint annotation="javax.validation.constraints.NotNull"/> </container-element-type> </container-element-type> </field> <getter name="passedVehicleInspection" ignore-annotations="true"> <constraint annotation="javax.validation.constraints.AssertTrue"> <message>The car has to pass the vehicle inspection first</message> <groups> <value>CarChecks</value> </groups> <element name="max">10</element> </constraint> </getter> </bean> <bean ignore-annotations="true"> <class ignore-annotations="true"> <group-sequence> <value>RentalCar</value> <value>CarChecks</value> </group-sequence> </class> </bean> <constraint-definition annotation="org.mycompany.CheckCase"> <validated-by include-existing-validators="false"> <value>org.mycompany.CheckCaseValidator</value> </validated-by> </constraint-definition></constraint-mappings>Example 8.3, “Method constraints configured via XML” shows how the constraints fromExample 3.1, “Declaring method and constructor parameter constraints”,Example 3.4, “Declaring method and constructor return value constraints”andExample 3.3, “Specifying a constraint’s target” can be expressed in XML.
<constraint-mappings xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd" version="2.0"> <default-package>org.hibernate.validator.referenceguide.chapter08</default-package> <bean ignore-annotations="true"> <constructor> <return-value> <constraint annotation="ValidRentalStation"/> </return-value> </constructor> <constructor> <parameter type="java.lang.String"> <constraint annotation="javax.validation.constraints.NotNull"/> </parameter> </constructor> <method name="getCustomers"> <return-value> <constraint annotation="javax.validation.constraints.NotNull"/> <constraint annotation="javax.validation.constraints.Size"> <element name="min">1</element> </constraint> </return-value> </method> <method name="rentCar"> <parameter type="Customer"> <constraint annotation="javax.validation.constraints.NotNull"/> </parameter> <parameter type="java.util.Date"> <constraint annotation="javax.validation.constraints.NotNull"/> <constraint annotation="javax.validation.constraints.Future"/> </parameter> <parameter type="int"> <constraint annotation="javax.validation.constraints.Min"> <element name="value">1</element> </constraint> </parameter> </method> <method name="addCars"> <parameter type="java.util.List"> <container-element-type> <valid/> <constraint annotation="javax.validation.constraints.NotNull"/> </container-element-type> </parameter> </method> </bean> <bean ignore-annotations="true"> <method name="buildCar"> <parameter type="java.util.List"/> <cross-parameter> <constraint annotation="ELAssert"> <element name="expression">...</element> <element name="validationAppliesTo">PARAMETERS</element> </constraint> </cross-parameter> </method> <method name="paintCar"> <parameter type="int"/> <return-value> <constraint annotation="ELAssert"> <element name="expression">...</element> <element name="validationAppliesTo">RETURN_VALUE</element> </constraint> </return-value> </method> </bean></constraint-mappings>The XML configuration is closely mirroring the programmatic API. For this reason it should sufficeto just add some comments.default-package is used for all fields where a class name is expected. Ifthe specified class is not fully qualified the configured default package will be used. Everymapping file can then have several bean nodes, each describing the constraints on the entity withthe specified class name.
A given class can only be configured once across all configuration files. The same applies forconstraint definitions for a given constraint annotation. It can only occur in one mapping file. Ifthese rules are violated a |
Settingignore-annotations totrue means that constraint annotations placed on the configured beanare ignored. The default for this value is true.ignore-annotations is also available for the nodesclass,fields,getter,constructor,method,parameter,cross-parameter andreturn-value.If not explicitly specified on these levels the configured bean value applies.
The nodesclass,field,getter,container-element-type,constructor andmethod(and its sub node parameter) determine on which level the constraint gets placed.Thevalid node is used to enable cascaded validation and theconstraint node to add a constrainton the corresponding level.Each constraint definition must define the class via theannotation attribute.The constraint attributes required by the Jakarta Bean Validation specification (message,groups andpayload) have dedicated nodes. All other constraint specific attributes are configured using theelement node.
|
Theclass node also allows to reconfigure the default group sequence (seeSection 5.4, “Redefining the default group sequence”) via thegroup-sequence node. Not shown in the example is the useofconvert-group tospecify group conversions (seeSection 5.5, “Group conversion”). This node is available onfield,getter,container-element-type,parameter andreturn-value and specifies afrom and atoattributes to specify the groups.
Last but not least, the list ofConstraintValidator instances associated to a given constraintcan be altered via theconstraint-definition node. The annotation attribute represents the constraintannotation being altered. Thevalidated-by element represent the (ordered) list ofConstraintValidatorimplementations associated to the constraint. Ifinclude-existing-validator is set tofalse,validators defined on the constraint annotation are ignored. If set totrue, the list of constraintvalidators described in XML is concatenated to the list of validators specified on the annotation.
One use case for constraint-definition is to change the default constraint definition for Using XML to register a regular expression based constraint definition for @URL |
InSection 2.2.1, “Obtaining aValidator instance”, you already saw one way of creating aValidator instance - viaValidation#buildDefaultValidatorFactory(). In this chapter, you will learn how to use the othermethods injavax.validation.Validation in order to bootstrap specifically configured validators.
ValidatorFactory andValidatorYou obtain aValidator by retrieving aValidatorFactory via one of the static methods onjavax.validation.Validation and callinggetValidator() on the factory instance.
Example 9.1, “Bootstrapping defaultValidatorFactory andValidator” shows how to obtain a validator from the defaultvalidator factory:
ValidatorFactory andValidatorValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();Validator validator = validatorFactory.getValidator();The generated |
Jakarta Bean Validation supports working with several providers such as Hibernate Validator within oneapplication. If more than one provider is present on the classpath, it is not guaranteed which oneis chosen when creating a factory viabuildDefaultValidatorFactory().
In this case, you can explicitly specify the provider to use viaValidation#byProvider(), passing theprovider’sValidationProvider class as shown inExample 9.2, “BootstrappingValidatorFactory andValidator using a specific provider”.
ValidatorFactory andValidator using a specific providerValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Note that the configuration object returned byconfigure() allows to specifically customize thefactory before callingbuildValidatorFactory(). The available options are discussed later in thischapter.
Similarly you can retrieve the default validator factory for configuration which is demonstrated inExample 9.3, “Retrieving the defaultValidatorFactory for configuration”.
ValidatorFactory for configurationValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .buildValidatorFactory();Validator validator = validatorFactory.getValidator();If a |
ValidationProviderResolverBy default, available Jakarta Bean Validation providers are discovered using theJavaService Provider mechanism.
For that purpose, each provider includes the fileMETA-INF/services/javax.validation.spi.ValidationProvider, containing the fully qualified classname ofitsValidationProvider implementation. In the case of Hibernate Validator, this isorg.hibernate.validator.HibernateValidator.
Depending on your environment and its classloading specifics, provider discovery via the Java’sservice loader mechanism might not work. In this case, you can plug in a customValidationProviderResolver implementation which performs the provider retrieval. An example is OSGi,where you could implement a provider resolver which uses OSGi services for provider discovery.
To use a custom provider resolver, pass it viaproviderResolver() as shown inExample 9.4, “Using a customValidationProviderResolver”.
ValidationProviderResolverpackage org.hibernate.validator.referenceguide.chapter09;public class OsgiServiceDiscoverer implements ValidationProviderResolver { @Override public List<ValidationProvider<?>> getValidationProviders() { //... return null; }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .providerResolver( new OsgiServiceDiscoverer() ) .configure() .buildValidatorFactory();Validator validator = validatorFactory.getValidator();ValidatorFactoryBy default, validator factories retrieved fromValidation and any validators they create areconfigured as per the XML descriptorMETA-INF/validation.xml (seeChapter 8,Configuring via XML),if present.
If you want to disable the XML based configuration, you can do so by invokingConfiguration#ignoreXmlConfiguration().
The different values of the XML configuration can be accessed viaConfiguration#getBootstrapConfiguration(). This can for instance be helpful if you want to integrateJakarta Bean Validation into a managed environment and want to create managed instances of the objectsconfigured via XML.
Using the fluent configuration API, you can override one or more of the settings when bootstrappingthe factory. The following sections show how to make use of the different options. Note that theConfiguration class exposes the default implementations of the different extension points which canbe useful if you want to use these as delegates for your custom implementations.
MessageInterpolatorMessage interpolators are used by the validation engine to create user readable error messages fromconstraint message descriptors.
In case the default message interpolation algorithm described inChapter 4,Interpolating constraint error messagesis not sufficient for your needs, you can pass in your own implementation of theMessageInterpolatorinterface viaConfiguration#messageInterpolator() as shown inExample 9.5, “Using a customMessageInterpolator”.
MessageInterpolatorpackage org.hibernate.validator.referenceguide.chapter09;public class MyMessageInterpolator implements MessageInterpolator { @Override public String interpolate(String messageTemplate, Context context) { //... return null; } @Override public String interpolate(String messageTemplate, Context context, Locale locale) { //... return null; }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .messageInterpolator( new MyMessageInterpolator() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();TraversableResolverIn some cases the validation engine should not access the state of a bean property.The most obvious example for that is a lazily loaded property or association of a JPA entity.Validating this lazy property or association would mean that its state would have to be accessed,triggering a load from the database.
Which properties can be accessed and which ones not is controlled by querying theTraversableResolver interface.Example 9.6, “Using a customTraversableResolver” shows how to use acustom traversable resolver implementation.
TraversableResolverpackage org.hibernate.validator.referenceguide.chapter09;public class MyTraversableResolver implements TraversableResolver { @Override public boolean isReachable( Object traversableObject, Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) { //... return false; } @Override public boolean isCascadable( Object traversableObject, Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) { //... return false; }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .traversableResolver( new MyTraversableResolver() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();If no specific traversable resolver has been configured, the default behavior is to consider all properties as reachable and cascadable.When using Hibernate Validator together with a JPA 2 provider such as Hibernate ORM, only those properties will be considered reachablewhich already have been loaded by the persistence provider and all properties will be considered cascadable.
By default, the traversable resolver calls are cached per validation call.This is especially important in a JPA environment where callingisReachable() has a significant cost.
This caching adds some overhead.In the case your custom traversable resolver is very fast, it might be better to consider turning off the cache.
You can disable the cache either via the XML configuration:
TraversableResolver result cache via the XML configuration<?xml version="1.0" encoding="UTF-8"?><validation-config xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd" version="2.0"> <default-provider>org.hibernate.validator.HibernateValidator</default-provider> <property name="hibernate.validator.enable_traversable_resolver_result_cache">false</property></validation-config>or via the programmatic API:
TraversableResolver result cache via the programmatic APIValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .traversableResolver( new MyFastTraversableResolver() ) .enableTraversableResolverResultCache( false ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();ConstraintValidatorFactoryConstraintValidatorFactory is the extension point for customizing how constraint validators areinstantiated and released.
The defaultConstraintValidatorFactory provided by Hibernate Validator requires a public no-argconstructor to instantiateConstraintValidator instances (seeSection 6.1.2, “The constraint validator”).Using a customConstraintValidatorFactory offers for example the possibility to use dependencyinjection in constraint validator implementations.
To configure a custom constraint validator factory callConfiguration#constraintValidatorFactory()(seeExample 9.9, “Using a customConstraintValidatorFactory”.
ConstraintValidatorFactorypackage org.hibernate.validator.referenceguide.chapter09;public class MyConstraintValidatorFactory implements ConstraintValidatorFactory { @Override public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { //... return null; } @Override public void releaseInstance(ConstraintValidator<?, ?> instance) { //... }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .constraintValidatorFactory( new MyConstraintValidatorFactory() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Any constraint implementations relying on |
|
ParameterNameProviderIn case a method or constructor parameter constraint is violated, theParameterNameProviderinterface is used to retrieve the parameter name and make it available to the user via theproperty path of the constraint violation.
The default implementation returns parameter names as obtained through the Java reflection API.If you compile your sources using the-parameters compiler flag, the actual parameter names asin the source code will be returned. Otherwise synthetic names in the form ofarg0,arg1 etc.will be used.
To use a custom parameter name provider either pass an instance ofthe provider during bootstrapping as shown inExample 9.10, “Using a customParameterNameProvider”,or specify the fully qualified class name of the provider as value forthe<parameter-name-provider> element in theMETA-INF/validation.xml file(seeSection 8.1, “Configuring the validator factory invalidation.xml”). This is demonstrated inExample 9.10, “Using a customParameterNameProvider”.
ParameterNameProviderpackage org.hibernate.validator.referenceguide.chapter09;public class MyParameterNameProvider implements ParameterNameProvider { @Override public List<String> getParameterNames(Constructor<?> constructor) { //... return null; } @Override public List<String> getParameterNames(Method method) { //... return null; }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .parameterNameProvider( new MyParameterNameProvider() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Hibernate Validator comes with a custom |
ClockProvider and temporal validation toleranceFor time related validation (@Past and@Future constraints for instance), it might be useful to define what isconsiderednow.
This is especially important when you want to test your constraints in a reliable manner.
The reference time is defined by theClockProvider contract. The responsibility of theClockProvider is toprovide ajava.time.Clock definingnow for time related validators.
ClockProviderpackage org.hibernate.validator.referenceguide.chapter09;public class FixedClockProvider implements ClockProvider { private Clock clock; public FixedClockProvider(ZonedDateTime dateTime) { clock = Clock.fixed( dateTime.toInstant(), dateTime.getZone() ); } @Override public Clock getClock() { return clock; }}ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .clockProvider( new FixedClockProvider( ZonedDateTime.of( 2016, 6, 15, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ) ) ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Alternatively, you can specify the fully-qualified classname of aClockProvider implementation using the<clock-provider> element when configuring the default validator factory viaMETA-INF/validation.xml(seeChapter 8,Configuring via XML).
When validating You can obtain the For instance, this might be useful if you want to replace the default message of the |
When dealing with distributed architectures, you might need some tolerance when applying temporal constraintssuch as@Past or@Future.
You can set a temporal validation tolerance by bootstrapping yourValidatorFactory as below:
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .temporalValidationTolerance( Duration.ofMillis( 10 ) ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Alternatively, you can define it in the XML configuration by setting thehibernate.validator.temporal_validation_tolerance propertyin yourMETA-INF/validation.xml.
The value of this property must be along defining the tolerance in milliseconds.
When implementing your own temporal constraints, you might need to have access to the temporal validation tolerance. It can be obtained by calling the Note that to get access to this context at initialization, your constraint validator has to implementthe |
ValueExtractorsAs mentioned inChapter 7,Value extraction, additional value extractors can be registered during bootstrapping(seeSection 7.5, “Registering aValueExtractor” for the other ways to register a value extractor).
Example 9.13, “Registering additional value extractors” shows how we would register the value extractors we previously createdto extract the keys and the values of Guava’sMultimap.
ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .addValueExtractor( new MultimapKeyValueExtractor() ) .addValueExtractor( new MultimapValueValueExtractor() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();As discussed earlier, you can configure the constraints applied to your Java beans using XML basedconstraint mappings.
Besides the mapping files specified inMETA-INF/validation.xml, you can add further mappings viaConfiguration#addMapping() (seeExample 9.14, “Adding constraint mapping streams”). Note that the passed inputstream(s) must adhere to the XML schema for constraint mappings presented inSection 8.2, “Mapping constraints viaconstraint-mappings”.
InputStream constraintMapping1 = null;InputStream constraintMapping2 = null;ValidatorFactory validatorFactory = Validation.byDefaultProvider() .configure() .addMapping( constraintMapping1 ) .addMapping( constraintMapping2 ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();You should close any passed input stream after the validator factory has been created.
Via the configuration object returned byValidation#byProvider(), provider specific options can beconfigured.
In the case of Hibernate Validator, this e.g. allows you to enable the fail fast mode and pass one ormore programmatic constraint mappings as demonstrated inExample 9.15, “Setting Hibernate Validator specific options”.
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .failFast( true ) .addMapping( (ConstraintMapping) null ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Alternatively, provider-specific options can be passed viaConfiguration#addProperty(). HibernateValidator supports enabling the fail fast mode that way, too:
addProperty()ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();Refer toSection 12.2, “Fail fast mode” andSection 12.4, “Programmatic constraint definition and declaration” to learn more about the fail fastmode and the constraint declaration API.
ScriptEvaluatorFactoryFor constraints like@ScriptAssert and@ParameterScriptAssert, it might be useful to configurehow the script engines are initialized and how the script evaluators are built.This can be done by setting a custom implementation ofScriptEvaluatorFactory.
In particular, this is important for modular environments (e.g. OSGi), where user might face issueswith modular class loading andJSR 223.It also allows to use any custom script engine, not necessarily based on theJSR 223 (e.g. Spring Expression Language).
To specify theScriptEvaluatorFactory via XML, you need to define thehibernate.validator.script_evaluator_factoryproperty.
ScriptEvaluatorFactory via XML<validation-config xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd" version="2.0"> <property name="hibernate.validator.script_evaluator_factory"> org.hibernate.validator.referenceguide.chapter09.CustomScriptEvaluatorFactory </property></validation-config>In this case, the specifiedScriptEvaluatorFactory must have a no-arg constructor.
To configure it programmatically, you need to pass an instance ofScriptEvaluatorFactory to theValidatorFactory.This gives more flexibility in the configuration of theScriptEvaluatorFactory.Example 9.18, “Defining theScriptEvaluatorFactory programmatically”shows how this can be done.
ScriptEvaluatorFactory programmaticallyValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .scriptEvaluatorFactory( new CustomScriptEvaluatorFactory() ) .buildValidatorFactory();Validator validator = validatorFactory.getValidator();ScriptEvaluatorFactory implementation examplesThis section shows a couple of customScriptEvaluatorFactory implementations that can be usedin modular environments as well as one using theSpring Expression Languagefor writing constraint scripts.
Problems with modular environments andJSR 223 come from the class loading.The class loader where the script engine is available might be different from the one of Hibernate Validator.Thus the script engine wouldn’t be found using the default strategy.
To solve this issue, theMultiClassLoaderScriptEvaluatorFactory class below can be introduced:
/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */package org.hibernate.validator.osgi.scripting;import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;import org.hibernate.validator.spi.scripting.ScriptEvaluationException;import org.hibernate.validator.spi.scripting.ScriptEvaluator;import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;/** * {@link ScriptEvaluatorFactory} that allows you to pass multiple {@link ClassLoader}s that will be used * to search for {@link ScriptEngine}s. Useful in environments similar to OSGi, where script engines can be * found only in {@link ClassLoader}s different from default one. * * @author Marko Bekhta */public class MultiClassLoaderScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory { private final ClassLoader[] classLoaders; public MultiClassLoaderScriptEvaluatorFactory(ClassLoader... classLoaders) { if ( classLoaders.length == 0 ) { throw new IllegalArgumentException( "No class loaders were passed" ); } this.classLoaders = classLoaders; } @Override protected ScriptEvaluator createNewScriptEvaluator(String languageName) { for ( ClassLoader classLoader : classLoaders ) { ScriptEngine engine = new ScriptEngineManager( classLoader ).getEngineByName( languageName ); if ( engine != null ) { return new ScriptEngineScriptEvaluator( engine ); } } throw new ScriptEvaluationException( "No JSR 223 script engine found for language " + languageName ); }}and then declared with:
Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .scriptEvaluatorFactory( new MultiClassLoaderScriptEvaluatorFactory( GroovyScriptEngineFactory.class.getClassLoader() ) ) .buildValidatorFactory() .getValidator();This way, it is possible to pass multipleClassLoader instances: typically the class loaders of the wantedScriptEngines.
An alternative approach for OSGi environments can be to use theOsgiScriptEvaluatorFactory defined below:
/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */package org.hibernate.validator.osgi.scripting;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import java.util.Arrays;import java.util.Collections;import java.util.Enumeration;import java.util.List;import java.util.Objects;import java.util.stream.Collectors;import java.util.stream.Stream;import javax.script.ScriptEngineFactory;import javax.script.ScriptEngineManager;import javax.validation.ValidationException;import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;import org.hibernate.validator.spi.scripting.ScriptEvaluator;import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;import org.hibernate.validator.spi.scripting.ScriptEvaluatorNotFoundException;import org.osgi.framework.Bundle;import org.osgi.framework.BundleContext;/** * {@link ScriptEvaluatorFactory} suitable for OSGi environments. It is created * based on the {@code BundleContext} which is used to iterate through {@code Bundle}s and find all {@link ScriptEngineFactory} * candidates. * * @author Marko Bekhta */public class OsgiScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory { private final List<ScriptEngineManager> scriptEngineManagers; public OsgiScriptEvaluatorFactory(BundleContext context) { this.scriptEngineManagers = Collections.unmodifiableList( findManagers( context ) ); } @Override protected ScriptEvaluator createNewScriptEvaluator(String languageName) throws ScriptEvaluatorNotFoundException { return scriptEngineManagers.stream() .map( manager -> manager.getEngineByName( languageName ) ) .filter( Objects::nonNull ) .map( engine -> new ScriptEngineScriptEvaluator( engine ) ) .findFirst() .orElseThrow( () -> new ValidationException( String.format( "Unable to find script evaluator for '%s'.", languageName ) ) ); } private List<ScriptEngineManager> findManagers(BundleContext context) { return findFactoryCandidates( context ).stream() .map( className -> { try { return new ScriptEngineManager( Class.forName( className ).getClassLoader() ); } catch (ClassNotFoundException e) { throw new ValidationException( "Unable to instantiate '" + className + "' based engine factory manager.", e ); } } ).collect( Collectors.toList() ); } /** * Iterates through all bundles to get the available {@link ScriptEngineFactory} classes * * @return the names of the available ScriptEngineFactory classes * * @throws IOException */ private List<String> findFactoryCandidates(BundleContext context) { return Arrays.stream( context.getBundles() ) .filter( Objects::nonNull ) .filter( bundle -> !"system.bundle".equals( bundle.getSymbolicName() ) ) .flatMap( this::toStreamOfResourcesURL ) .filter( Objects::nonNull ) .flatMap( url -> toListOfFactoryCandidates( url ).stream() ) .collect( Collectors.toList() ); } private Stream<URL> toStreamOfResourcesURL(Bundle bundle) { Enumeration<URL> entries = bundle.findEntries( "META-INF/services", "javax.script.ScriptEngineFactory", false ); return entries != null ? Collections.list( entries ).stream() : Stream.empty(); } private List<String> toListOfFactoryCandidates(URL url) { try ( BufferedReader reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) ) ) { return reader.lines() .map( String::trim ) .filter( line -> !line.isEmpty() ) .filter( line -> !line.startsWith( "#" ) ) .collect( Collectors.toList() ); } catch (IOException e) { throw new ValidationException( "Unable to read the ScriptEngineFactory resource file", e ); } }}and then declared with:
Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .scriptEvaluatorFactory( new OsgiScriptEvaluatorFactory( FrameworkUtil.getBundle( this.getClass() ).getBundleContext() ) ) .buildValidatorFactory() .getValidator();It is designed specifically for OSGi environments and allows you to pass theBundleContext which will be used to searchforScriptEngineFactory as a parameter.
As already mentioned, you can also use script engines that are not based onJSR 223.
For instance, to use theSpring Expression Language,you can define aSpringELScriptEvaluatorFactory as:
package org.hibernate.validator.referenceguide.chapter09;public class SpringELScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory { @Override public ScriptEvaluator createNewScriptEvaluator(String languageName) { if ( !"spring".equalsIgnoreCase( languageName ) ) { throw new IllegalStateException( "Only Spring EL is supported" ); } return new SpringELScriptEvaluator(); } private static class SpringELScriptEvaluator implements ScriptEvaluator { private final ExpressionParser expressionParser = new SpelExpressionParser(); @Override public Object evaluate(String script, Map<String, Object> bindings) throws ScriptEvaluationException { try { Expression expression = expressionParser.parseExpression( script ); EvaluationContext context = new StandardEvaluationContext( bindings.values().iterator().next() ); for ( Entry<String, Object> binding : bindings.entrySet() ) { context.setVariable( binding.getKey(), binding.getValue() ); } return expression.getValue( context ); } catch (ParseException | EvaluationException e) { throw new ScriptEvaluationException( "Unable to evaluate SpEL script", e ); } } }}This factory allows to use Spring Expression Language inScriptAssert andParameterScriptAssertconstraints:
@ScriptAssert(script = "value > 0", lang = "spring")public class Foo { private final int value; private Foo(int value) { this.value = value; } public int getValue() { return value; }}When working with a configured validator factory it can occasionally be required to apply adifferent configuration to a singleValidator instance.Example 9.25, “Configuring aValidator instance viausingContext()” shows how this canbe achieved by callingValidatorFactory#usingContext().
Validator instance viausingContext()ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();Validator validator = validatorFactory.usingContext() .messageInterpolator( new MyMessageInterpolator() ) .traversableResolver( new MyTraversableResolver() ) .getValidator();The Jakarta Bean Validation specification provides not only a validation engine, but also an API forretrieving constraint metadata in a uniform way, no matter whether the constraints are declaredusing annotations or via XML mappings. Read this chapter to learn more about this API and itspossibilities. You can find all the metadata API types in the packagejavax.validation.metadata.
The examples presented in this chapter are based on the classes and constraint declarations shown inExample 10.1, “Example classes”.
package org.hibernate.validator.referenceguide.chapter10;public class Person { public interface Basic { } @NotNull private String name; //getters and setters ...}package org.hibernate.validator.referenceguide.chapter10;public interface Vehicle { public interface Basic { } @NotNull(groups = Vehicle.Basic.class) String getManufacturer();}package org.hibernate.validator.referenceguide.chapter10;@ValidCarpublic class Car implements Vehicle { public interface SeverityInfo extends Payload { } private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; private Person driver; private String modelName; public Car() { } public Car( @NotNull String manufacturer, String licencePlate, Person driver, String modelName) { this.manufacturer = manufacturer; this.licensePlate = licencePlate; this.driver = driver; this.modelName = modelName; } public void driveAway(@Max(75) int speed) { //... } @LuggageCountMatchesPassengerCount( piecesOfLuggagePerPassenger = 2, validationAppliesTo = ConstraintTarget.PARAMETERS, payload = SeverityInfo.class, message = "There must not be more than {piecesOfLuggagePerPassenger} pieces " + "of luggage per passenger." ) public void load(List<Person> passengers, List<PieceOfLuggage> luggage) { //... } @Override @Size(min = 3) public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } @Valid @ConvertGroup(from = Default.class, to = Person.Basic.class) public Person getDriver() { return driver; } //further getters and setters...}package org.hibernate.validator.referenceguide.chapter10;public class Library { @NotNull private String name; private List<@NotNull @Valid Book> books; //getters and setters ...}package org.hibernate.validator.referenceguide.chapter10;public class Book { @NotEmpty private String title; @NotEmpty private String author; //getters and setters ...}BeanDescriptorThe entry point into the metadata API is the methodValidator#getConstraintsForClass(), whichreturns an instance of theBeanDescriptor interface. Using thisdescriptor, you can obtain metadata for constraints declared directly on the bean itself (class- orproperty-level), but also retrieve metadata descriptors representing single properties, methods andconstructors.
Example 10.2, “UsingBeanDescriptor” demonstrates how to retrieve aBeanDescriptor for theCar class and how to use this descriptor in form of assertions.
If a constraint declaration hosted by the requested class is invalid, a |
BeanDescriptorBeanDescriptor carDescriptor = validator.getConstraintsForClass( Car.class );assertTrue( carDescriptor.isBeanConstrained() );//one class-level constraintassertEquals( 1, carDescriptor.getConstraintDescriptors().size() );//manufacturer, licensePlate, driverassertEquals( 3, carDescriptor.getConstrainedProperties().size() );//property has constraintassertNotNull( carDescriptor.getConstraintsForProperty( "licensePlate" ) );//property is marked with @ValidassertNotNull( carDescriptor.getConstraintsForProperty( "driver" ) );//constraints from getter method in interface and implementation class are returnedassertEquals( 2, carDescriptor.getConstraintsForProperty( "manufacturer" ) .getConstraintDescriptors() .size());//property is not constrainedassertNull( carDescriptor.getConstraintsForProperty( "modelName" ) );//driveAway(int), load(List<Person>, List<PieceOfLuggage>)assertEquals( 2, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER ).size() );//driveAway(int), getManufacturer(), getDriver(), load(List<Person>, List<PieceOfLuggage>)assertEquals( 4, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER, MethodType.GETTER ) .size());//driveAway(int)assertNotNull( carDescriptor.getConstraintsForMethod( "driveAway", int.class ) );//getManufacturer()assertNotNull( carDescriptor.getConstraintsForMethod( "getManufacturer" ) );//setManufacturer() is not constrainedassertNull( carDescriptor.getConstraintsForMethod( "setManufacturer", String.class ) );//Car(String, String, Person, String)assertEquals( 1, carDescriptor.getConstrainedConstructors().size() );//Car(String, String, Person, String)assertNotNull( carDescriptor.getConstraintsForConstructor( String.class, String.class, Person.class, String.class ));You can determine whether the specified class hosts any class- or property-level constraints viaisBeanConstrained(). Method or constructor constraints are not considered byisBeanConstrained().
The methodgetConstraintDescriptors() is common to all descriptors derived fromElementDescriptor(seeSection 10.4, “ElementDescriptor”) and returns a set of descriptors representing theconstraints directly declared on the given element. In case ofBeanDescriptor, the bean’s class-level constraints are returned. More details onConstraintDescriptor can be found inSection 10.7, “ConstraintDescriptor”.
ViagetConstraintsForProperty(),getConstraintsForMethod() andgetConstraintsForConstructor() youcan obtain a descriptor representing one given property or executable element, identified by itsname and, in case of methods and constructors, parameter types. The different descriptor typesreturned by these methods are described in the following sections.
Note that these methods consider constraints declared at super-types according to the rules forconstraint inheritance as described inSection 2.1.5, “Constraint inheritance”. An example is thedescriptor for themanufacturer property, which provides access to all constraints defined onVehicle#getManufacturer() and the implementing methodCar#getManufacturer().null is returned incase the specified element does not exist or is not constrained.
The methodsgetConstrainedProperties(),getConstrainedMethods() andgetConstrainedConstructors()return (potentially empty) sets with all constrained properties, methods and constructors,respectively. An element is considered constrained if it has at least one constraint or is markedfor cascaded validation. When invokinggetConstrainedMethods(), you can specify the type of themethods to be returned (getters, non-getters or both).
PropertyDescriptorThe interfacePropertyDescriptor represents one given property of aclass. It is transparent whether constraints are declared on a field or a property getter, providedthe JavaBeans naming conventions are respected.Example 10.3, “UsingPropertyDescriptor” showshow to use thePropertyDescriptor interface.
PropertyDescriptorPropertyDescriptor licensePlateDescriptor = carDescriptor.getConstraintsForProperty( "licensePlate");//"licensePlate" has two constraints, is not marked with @Valid and defines no group conversionsassertEquals( "licensePlate", licensePlateDescriptor.getPropertyName() );assertEquals( 2, licensePlateDescriptor.getConstraintDescriptors().size() );assertTrue( licensePlateDescriptor.hasConstraints() );assertFalse( licensePlateDescriptor.isCascaded() );assertTrue( licensePlateDescriptor.getGroupConversions().isEmpty() );PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );//"driver" has no constraints, is marked with @Valid and defines one group conversionassertEquals( "driver", driverDescriptor.getPropertyName() );assertTrue( driverDescriptor.getConstraintDescriptors().isEmpty() );assertFalse( driverDescriptor.hasConstraints() );assertTrue( driverDescriptor.isCascaded() );assertEquals( 1, driverDescriptor.getGroupConversions().size() );UsinggetConstraintDescriptors(), you can retrieve a set ofConstraintDescriptors providing moreinformation on the individual constraints of a given property. The methodisCascaded() returnstrue if the property is marked for cascaded validation (either using the@Valid annotation or viaXML),false otherwise. Any configured group conversions are returned bygetGroupConversions(). SeeSection 10.6, “GroupConversionDescriptor” for more details onGroupConversionDescriptor.
MethodDescriptor andConstructorDescriptorConstrained methods and constructors are represented by the interfacesMethodDescriptorConstructorDescriptor, respectively.Example 10.4, “UsingMethodDescriptor andConstructorDescriptor” demonstrates how to work with thesedescriptors.
MethodDescriptor andConstructorDescriptor//driveAway(int) has a constrained parameter and an unconstrained return valueMethodDescriptor driveAwayDescriptor = carDescriptor.getConstraintsForMethod( "driveAway", int.class);assertEquals( "driveAway", driveAwayDescriptor.getName() );assertTrue( driveAwayDescriptor.hasConstrainedParameters() );assertFalse( driveAwayDescriptor.hasConstrainedReturnValue() );//always returns an empty set; constraints are retrievable by navigating to//one of the sub-descriptors, e.g. for the return valueassertTrue( driveAwayDescriptor.getConstraintDescriptors().isEmpty() );ParameterDescriptor speedDescriptor = driveAwayDescriptor.getParameterDescriptors() .get( 0 );//The "speed" parameter is located at index 0, has one constraint and is not cascaded//nor does it define group conversionsassertEquals( "speed", speedDescriptor.getName() );assertEquals( 0, speedDescriptor.getIndex() );assertEquals( 1, speedDescriptor.getConstraintDescriptors().size() );assertFalse( speedDescriptor.isCascaded() );assert speedDescriptor.getGroupConversions().isEmpty();//getDriver() has no constrained parameters but its return value is marked for cascaded//validation and declares one group conversionMethodDescriptor getDriverDescriptor = carDescriptor.getConstraintsForMethod( "getDriver");assertFalse( getDriverDescriptor.hasConstrainedParameters() );assertTrue( getDriverDescriptor.hasConstrainedReturnValue() );ReturnValueDescriptor returnValueDescriptor = getDriverDescriptor.getReturnValueDescriptor();assertTrue( returnValueDescriptor.getConstraintDescriptors().isEmpty() );assertTrue( returnValueDescriptor.isCascaded() );assertEquals( 1, returnValueDescriptor.getGroupConversions().size() );//load(List<Person>, List<PieceOfLuggage>) has one cross-parameter constraintMethodDescriptor loadDescriptor = carDescriptor.getConstraintsForMethod( "load", List.class, List.class);assertTrue( loadDescriptor.hasConstrainedParameters() );assertFalse( loadDescriptor.hasConstrainedReturnValue() );assertEquals( 1, loadDescriptor.getCrossParameterDescriptor().getConstraintDescriptors().size());//Car(String, String, Person, String) has one constrained parameterConstructorDescriptor constructorDescriptor = carDescriptor.getConstraintsForConstructor( String.class, String.class, Person.class, String.class);assertEquals( "Car", constructorDescriptor.getName() );assertFalse( constructorDescriptor.hasConstrainedReturnValue() );assertTrue( constructorDescriptor.hasConstrainedParameters() );assertEquals( 1, constructorDescriptor.getParameterDescriptors() .get( 0 ) .getConstraintDescriptors() .size());getName() returns the name of the given method or constructor. The methodshasConstrainedParameters() andhasConstrainedReturnValue() can be used to perform a quick checkwhether an executable element has any parameter constraints (either constraints on single parametersor cross-parameter constraints) or return value constraints.
Note that constraints are not directly exposed onMethodDescriptor andConstructorDescriptor,but rather on dedicated descriptors representing an executable’s parameters, its return value andits cross-parameter constraints. To get hold of one of these descriptors, invokegetParameterDescriptors(),getReturnValueDescriptor() orgetCrossParameterDescriptor(),respectively.
These descriptors provide access to the element’s constraints (getConstraintDescriptors()) and, inthe case of parameters and return value, to its configuration for cascaded validation (isValid() andgetGroupConversions()). For parameters, you also can retrieve the index and the name, as returned bythe currently used parameter name provider (seeSection 9.2.4, “ParameterNameProvider”) viagetName()andgetIndex().
Getter methods following the JavaBeans naming conventions are considered as bean properties but alsoas constrained methods. That means you can retrieve the related metadata either by obtaining a |
ElementDescriptorTheElementDescriptorinterface is the common base class for theindividual descriptor types such asBeanDescriptor,PropertyDescriptor etc. BesidesgetConstraintDescriptors() it provides some more methods common to all descriptors.
hasConstraints() allows for a quick check whether an element has any direct constraints (e.g. class-level constraints in case ofBeanDescriptor).
getElementClass() returns the Java type of the elementrepresented by a given descriptor. More specifically, the method returns
the object type when invoked onBeanDescriptor,
the type of a property or parameter when invoked onPropertyDescriptor orParameterDescriptorrespectively,
Object[].class when invoked onCrossParameterDescriptor,
the return type when invoked onConstructorDescriptor,MethodDescriptor orReturnValueDescriptor.void.class will be returned for methods which don’t have a return value.
Example 10.5, “UsingElementDescriptor methods” shows how these methods are used.
ElementDescriptor methodsPropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty( "manufacturer");assertTrue( manufacturerDescriptor.hasConstraints() );assertEquals( String.class, manufacturerDescriptor.getElementClass() );CrossParameterDescriptor loadCrossParameterDescriptor = carDescriptor.getConstraintsForMethod( "load", List.class, List.class).getCrossParameterDescriptor();assertTrue( loadCrossParameterDescriptor.hasConstraints() );assertEquals( Object[].class, loadCrossParameterDescriptor.getElementClass() );Finally,ElementDescriptor offers access to theConstraintFinder API which allows you to query forconstraint metadata in a fine grained way.Example 10.6, “Usage ofConstraintFinder” shows how to retrieve aConstraintFinder instance viafindConstraints() and use the API to query for constraint metadata.
ConstraintFinderPropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty( "manufacturer");//"manufacturer" constraints are declared on the getter, not the fieldassertTrue( manufacturerDescriptor.findConstraints() .declaredOn( ElementType.FIELD ) .getConstraintDescriptors() .isEmpty());//@NotNull on Vehicle#getManufacturer() is part of another groupassertEquals( 1, manufacturerDescriptor.findConstraints() .unorderedAndMatchingGroups( Default.class ) .getConstraintDescriptors() .size());//@Size on Car#getManufacturer()assertEquals( 1, manufacturerDescriptor.findConstraints() .lookingAt( Scope.LOCAL_ELEMENT ) .getConstraintDescriptors() .size());//@Size on Car#getManufacturer() and @NotNull on Vehicle#getManufacturer()assertEquals( 2, manufacturerDescriptor.findConstraints() .lookingAt( Scope.HIERARCHY ) .getConstraintDescriptors() .size());//Combining several filter optionsassertEquals( 1, manufacturerDescriptor.findConstraints() .declaredOn( ElementType.METHOD ) .lookingAt( Scope.HIERARCHY ) .unorderedAndMatchingGroups( Vehicle.Basic.class ) .getConstraintDescriptors() .size());ViadeclaredOn() you can search forConstraintDescriptors declared on certain element types. This isuseful to find property constraints declared on either fields or getter methods.
unorderedAndMatchingGroups() restricts the resulting constraints to those matching the givenvalidation group(s).
lookingAt() allows to distinguish between constraints directly specified on the element(Scope.LOCAL_ELEMENT) or constraints belonging to the element but hosted anywhere in the classhierarchy (Scope.HIERARCHY).
You can also combine the different options as shown in the last example.
Order is not respected by |
ContainerDescriptor andContainerElementTypeDescriptorTheContainerDescriptorinterface is the common interface for all the elements that support container element constraints and cascadingvalidation (PropertyDescriptor,ParameterDescriptor,ReturnValueDescriptor).
It has a single methodgetConstrainedContainerElementTypes() that returns a set ofContainerElementTypeDescriptor.
ContainerElementTypeDescriptor extendsContainerDescriptor to support nested container element constraints.
ContainerElementTypeDescriptor contains the information about the container, the constraints and the cascadingvalidation.
Example 10.7, “UsingContainerElementTypeDescriptor” shows how to usegetConstrainedContainerElementTypes()to retrieve the set ofContainerElementTypeDescriptor.
ContainerElementTypeDescriptorPropertyDescriptor booksDescriptor = libraryDescriptor.getConstraintsForProperty( "books");Set<ContainerElementTypeDescriptor> booksContainerElementTypeDescriptors = booksDescriptor.getConstrainedContainerElementTypes();ContainerElementTypeDescriptor booksContainerElementTypeDescriptor = booksContainerElementTypeDescriptors.iterator().next();assertTrue( booksContainerElementTypeDescriptor.hasConstraints() );assertTrue( booksContainerElementTypeDescriptor.isCascaded() );assertEquals( 0, booksContainerElementTypeDescriptor.getTypeArgumentIndex().intValue());assertEquals( List.class, booksContainerElementTypeDescriptor.getContainerClass());Set<ConstraintDescriptor<?>> constraintDescriptors = booksContainerElementTypeDescriptor.getConstraintDescriptors();ConstraintDescriptor<?> constraintDescriptor = constraintDescriptors.iterator().next();assertEquals( NotNull.class, constraintDescriptor.getAnnotation().annotationType());GroupConversionDescriptorAll those descriptor types that represent elements which can be subject of cascaded validation(i.e.,PropertyDescriptor,ParameterDescriptor andReturnValueDescriptor) provide access to theelement’s group conversions viagetGroupConversions(). The returned set contains aGroupConversionDescriptorfor each configured conversion, allowing to retrievesource and target groups of the conversion.Example 10.8, “UsingGroupConversionDescriptor”shows an example.
GroupConversionDescriptorPropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );Set<GroupConversionDescriptor> groupConversions = driverDescriptor.getGroupConversions();assertEquals( 1, groupConversions.size() );GroupConversionDescriptor groupConversionDescriptor = groupConversions.iterator() .next();assertEquals( Default.class, groupConversionDescriptor.getFrom() );assertEquals( Person.Basic.class, groupConversionDescriptor.getTo() );ConstraintDescriptorLast but not least, theConstraintDescriptorinterface describes asingle constraint together with its composing constraints. Via an instance of this interface you getaccess to the constraint annotation and its parameters.
Example 10.9, “UsingConstraintDescriptor”shows how to retrieve default constraint attributes (such as message template, groups etc.) as wellas custom constraint attributes (piecesOfLuggagePerPassenger) and other metadata such as theconstraint’s annotation type and its validators from aConstraintDescriptor.
ConstraintDescriptor//descriptor for the @LuggageCountMatchesPassengerCount constraint on the//load(List<Person>, List<PieceOfLuggage>) methodConstraintDescriptor<?> constraintDescriptor = carDescriptor.getConstraintsForMethod( "load", List.class, List.class).getCrossParameterDescriptor().getConstraintDescriptors().iterator().next();//constraint typeassertEquals( LuggageCountMatchesPassengerCount.class, constraintDescriptor.getAnnotation().annotationType());//standard constraint attributesassertEquals( SeverityInfo.class, constraintDescriptor.getPayload().iterator().next() );assertEquals( ConstraintTarget.PARAMETERS, constraintDescriptor.getValidationAppliesTo());assertEquals( Default.class, constraintDescriptor.getGroups().iterator().next() );assertEquals( "There must not be more than {piecesOfLuggagePerPassenger} pieces of luggage per " + "passenger.", constraintDescriptor.getMessageTemplate());//custom constraint attributeassertEquals( 2, constraintDescriptor.getAttributes().get( "piecesOfLuggagePerPassenger" ));//no composing constraintsassertTrue( constraintDescriptor.getComposingConstraints().isEmpty() );//validator classassertEquals( Arrays.<Class<?>>asList( LuggageCountMatchesPassengerCount.Validator.class ), constraintDescriptor.getConstraintValidatorClasses());Hibernate Validator is intended to be used to implement multi-layered data validation, whereconstraints are expressed in a single place (the annotated domain model) and checked in variousdifferent layers of the application. For this reason there are multiple integration points withother technologies.
Hibernate Validator integrates with both Hibernate ORM and all pure Java Persistence providers.
When lazy loaded associations are supposed to be validated it is recommended to place the constrainton the getter of the association. Hibernate ORM replaces lazy loaded associations with proxy instanceswhich get initialized/loaded when requested via the getter. If, in such a case, the constraint isplaced on field level, the actual proxy instance is used which will lead to validation errors. |
Out of the box, Hibernate ORM will translate the constraints you have defined foryour entities into mapping metadata. For example, if a property of your entity is annotated@NotNull, its columns will be declared asnot null in the DDL schema generated by Hibernate ORM.
If, for some reason, the feature needs to be disabled, sethibernate.validator.apply_to_ddl tofalse. See alsoSection 2.3.1, “Jakarta Bean Validation constraints” andSection 2.3.2, “Additional constraints”.
You can also limit the DDL constraint generation to a subset of the defined constraints by settingthe propertyorg.hibernate.validator.group.ddl. The property specifies the comma-separated, fullyspecified class names of the groups a constraint has to be part of in order to be considered for DDLschema generation.
Hibernate Validator has a built-in Hibernate event listener -org.hibernate.cfg.beanvalidation.BeanValidationEventListener -which is part of Hibernate ORM. Whenever aPreInsertEvent,PreUpdateEvent orPreDeleteEvent occurs, the listener will verify all constraints of the entityinstance and throw an exception if any constraint is violated. Per default, objects will be checkedbefore any inserts or updates are made by Hibernate ORM. Pre deletion events will per default nottrigger a validation. You can configure the groups to be validated per event type using thepropertiesjavax.persistence.validation.group.pre-persist,javax.persistence.validation.group.pre-update andjavax.persistence.validation.group.pre-remove.The values of these properties are the comma-separated fully specified class names of the groupsto validate.Example 11.1, “Manual configuration ofBeanValidationEvenListener” shows the default values for theseproperties. In this case they could also be omitted.
On constraint violation, the event will raise a runtimeConstraintViolationException which containsa set ofConstraintViolation instances describing each failure.
If Hibernate Validator is present in the classpath, Hibernate ORM will use it transparently.To avoid validation even though Hibernate Validator is in the classpath, setjavax.persistence.validation.mode to none.
If the beans are not annotated with validation annotations, there is no runtime performance cost. |
In case you need to manually set the event listeners for Hibernate ORM, use the followingconfiguration inhibernate.cfg.xml:
BeanValidationEvenListener<hibernate-configuration> <session-factory> ... <property name="javax.persistence.validation.group.pre-persist"> javax.validation.groups.Default </property> <property name="javax.persistence.validation.group.pre-update"> javax.validation.groups.Default </property> <property name="javax.persistence.validation.group.pre-remove"></property> ... <event type="pre-update"> <listener/> </event> <event type="pre-insert"> <listener/> </event> <event type="pre-delete"> <listener/> </event> </session-factory></hibernate-configuration>If you are using JPA 2 and Hibernate Validator is in the classpath, the JPA2 specification requiresthat Jakarta Bean Validation gets enabled. The propertiesjavax.persistence.validation.group.pre-persist,javax.persistence.validation.group.pre-update andjavax.persistence.validation.group.pre-remove asdescribed inSection 11.1.2, “Hibernate ORM event-based validation” can in this case be configured inpersistence.xml.persistence.xml also defines a node validation-mode which can be set toAUTO,CALLBACK orNONE. The default isAUTO.
When working with JSF2 or JBoss Seam and Hibernate Validator (Jakarta Bean Validation) is present in theruntime environment, validation is triggered for every field in the application.Example 11.2, “Usage of Jakarta Bean Validation within JSF2”shows an example of thef:validateBean tag in a JSF page. ThevalidationGroups attribute is optionaland can be used to specify a comma separated list of validation groups. The default isjavax.validation.groups.Default. For more information refer to the Seam documentation or the JSF 2specification.
<h:form> <f:validateBean validationGroups="javax.validation.groups.Default"> <h:inputText value=#{model.property}/> <h:selectOneRadio value=#{model.radioProperty}> ... </h:selectOneRadio> <!-- other input components here --> </f:validateBean></h:form>The integration between JSF 2 and Jakarta Bean Validation is described in the "Jakarta Bean Validation Integration"chapter ofJSR-314. It is interesting to know that JSF2 implements a custom javax.faces.validator.BeanValidator.MESSAGE={1}: {0}The default is: javax.faces.validator.BeanValidator.MESSAGE={0} |
As of version 1.1, Bean Validation (and therefore Jakarta Bean Validation) is integrated with CDI(Contexts and Dependency Injection for Jakarta EE).
This integration provides CDI managed beans forValidator andValidatorFactory and enablesdependency injection in constraint validators as well as custom message interpolators, traversableresolvers, constraint validator factories, parameter name providers, clock providers and valueextractors.
Furthermore, parameter and return value constraints on the methods and constructors of CDI managedbeans will automatically be validated upon invocation.
When your application runs on a Java EE container, this integration is enabled by default. Whenworking with CDI in a Servlet container or in a pure Java SE environment, you can use the CDIportable extension provided by Hibernate Validator. To do so, add the portable extension to yourclass path as described inSection 1.1.2, “CDI”.
CDI’s dependency injection mechanism makes it very easy to retrieveValidatorFactory andValidatorinstances and use them in your managed beans. Just annotate instance fields of your bean with@javax.inject.Inject as shown inExample 11.3, “Retrieving validator factory and validator via@Inject”.
@Injectpackage org.hibernate.validator.referenceguide.chapter11.cdi.validator;@ApplicationScopedpublic class RentalStation { @Inject private ValidatorFactory validatorFactory; @Inject private Validator validator; //...}The injected beans are the default validator factory and validator instances. In order to configurethem - e.g. to use a custom message interpolator - you can use the Jakarta Bean Validation XML descriptorsas discussed inChapter 8,Configuring via XML.
If you are working with several Jakarta Bean Validation providers, you can make sure that factory andvalidator from Hibernate Validator are injected by annotating the injection points with the@HibernateValidator qualifier which is demonstrated inExample 11.4, “Using the@HibernateValidator qualifier annotation”.
@HibernateValidator qualifier annotationpackage org.hibernate.validator.referenceguide.chapter11.cdi.validator.qualifier;@ApplicationScopedpublic class RentalStation { @Inject @HibernateValidator private ValidatorFactory validatorFactory; @Inject @HibernateValidator private Validator validator; //...}The fully-qualified name of the qualifier annotation is |
Via@Inject you also can inject dependencies into constraint validators and other Jakarta Bean Validationobjects such asMessageInterpolator implementations etc.
Example 11.5, “Constraint validator with injected bean”demonstrates how an injected CDI bean is used in aConstraintValidator implementation to determinewhether the given constraint is valid or not. As the example shows, you also can work with the@PostConstruct and@PreDestroy callbacks to implement any required construction and destructionlogic.
package org.hibernate.validator.referenceguide.chapter11.cdi.injection;public class ValidLicensePlateValidator implements ConstraintValidator<ValidLicensePlate, String> { @Inject private VehicleRegistry vehicleRegistry; @PostConstruct public void postConstruct() { //do initialization logic... } @PreDestroy public void preDestroy() { //do destruction logic... } @Override public void initialize(ValidLicensePlate constraintAnnotation) { } @Override public boolean isValid(String licensePlate, ConstraintValidatorContext constraintContext) { return vehicleRegistry.isValidLicensePlate( licensePlate ); }}The method interception facilities of CDI allow for a very tight integration with Jakarta Bean Validation’smethod validation functionality. Just put constraint annotations to the parameters and return valuesof the executables of your CDI beans and they will be validated automatically before (parameterconstraints) and after (return value constraints) a method or constructor is invoked.
Note that no explicit interceptor binding is required, instead the required method validationinterceptor will automatically be registered for all managed beans with constrained methods andconstructors.
The interceptor |
You can see an example inExample 11.6, “CDI managed beans with method-level constraints”.
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;@ApplicationScopedpublic class RentalStation { @Valid public RentalStation() { //... } @NotNull @Valid public Car rentCar( @NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays) { //... return null; } @NotNull List<Car> getAvailableCars() { //... return null; }}package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;@RequestScopedpublic class RentCarRequest { @Inject private RentalStation rentalStation; public void rentCar(String customerId, Date startDate, int duration) { //causes ConstraintViolationException rentalStation.rentCar( null, null, -1 ); }}Here theRentalStation bean hosts several method constraints. When invoking one of theRentalStationmethods from another bean such asRentCarRequest, the constraints of the invoked method areautomatically validated. If any illegal parameter values are passed as in the example, aConstraintViolationException will be thrown by the method interceptor, providing detailedinformation on the violated constraints. The same is the case if the method’s return value violatesany return value constraints.
Similarly, constructor constraints are validated automatically upon invocation. In the example theRentalStation object returned by the constructor will be validated since the constructor returnvalue is marked with@Valid.
Jakarta Bean Validation allows for a fine-grained control of the executable types which are automaticallyvalidated. By default, constraints on constructors and non-getter methods are validated. Thereforethe@NotNull constraint on the methodRentalStation#getAvailableCars() inExample 11.6, “CDI managed beans with method-level constraints” does not get validated when the method is invoked.
You have the following options to configure which types of executables are validated uponinvocation:
Configure the executable types globally via the XML descriptorMETA-INF/validation.xml;seeSection 8.1, “Configuring the validator factory invalidation.xml” for an example
Use the@ValidateOnExecution annotation on the executable or type level
If several sources of configuration are specified for a given executable,@ValidateOnExecution onthe executable level takes precedence over@ValidateOnExecution on the type level and@ValidateOnExecution generally takes precedence over the globally configured types inMETA-INF/validation.xml.
Example 11.7, “Using@ValidateOnExecution” shows how to use the@ValidateOnExecution annotation:
@ValidateOnExecutionpackage org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.configuration;@ApplicationScoped@ValidateOnExecution(type = ExecutableType.ALL)public class RentalStation { @Valid public RentalStation() { //... } @NotNull @Valid @ValidateOnExecution(type = ExecutableType.NONE) public Car rentCar( @NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays) { //... return null; } @NotNull public List<Car> getAvailableCars() { //... return null; }}Here the methodrentCar() won’t be validated upon invocation because it is annotated with@ValidateOnExecution(type = ExecutableType.NONE). In contrast, the constructor and themethodgetAvailableCars() will be validated due to@ValidateOnExecution(type =ExecutableType.ALL) being given on the type level.ExecutableType.ALL is a more compact form forexplicitly specifying all the typesCONSTRUCTORS,GETTER_METHODS andNON_GETTER_METHODS.
Executable validation can be turned off globally by specifying |
Note that when a method overrides or implements a super-type method, the configuration will be takenfrom that overridden or implemented method (as given via@ValidateOnExecution on the method itselfor on the super-type). This protects a client of the super-type method from an unexpected alterationof the configuration, e.g. disabling validation of an overridden executable in a sub-type.
In case a CDI managed bean overrides or implements a super-type method and this super-type methodhosts any constraints, it can happen that the validation interceptor is not properly registered withthe bean, resulting in the bean’s methods not being validated upon invocation. In this case you canspecify the executable typeIMPLICIT on the sub-class as shown inExample 11.8, “UsingExecutableType.IMPLICIT”, which makes sure that all required metadata is discoveredand the validation interceptor kicks in when the methods onExpressRentalStation are invoked.
ExecutableType.IMPLICITpackage org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;@ValidateOnExecution(type = ExecutableType.ALL)public interface RentalStation { @NotNull @Valid Car rentCar( @NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays); @NotNull List<Car> getAvailableCars();}package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;@ApplicationScoped@ValidateOnExecution(type = ExecutableType.IMPLICIT)public class ExpressRentalStation implements RentalStation { @Override public Car rentCar(Customer customer, Date startDate, @Min(1) int durationInDays) { //... return null; } @Override public List<Car> getAvailableCars() { //... return null; }}When your application runs on a Java EE application server such asWildFly,you also can obtainValidator andValidatorFactory instances via@Resource injection inmanaged objects such as EJBs etc., as shown inExample 11.9, “RetrievingValidator andValidatorFactory via@Resource injection”.
Validator andValidatorFactory via@Resource injectionpackage org.hibernate.validator.referenceguide.chapter11.javaee;public class RentalStationBean { @Resource private ValidatorFactory validatorFactory; @Resource private Validator validator; //...}Alternatively you can obtain a validator and a validator factory from JNDI under the names"java:comp/Validator" and "java:comp/ValidatorFactory", respectively.
Similar to CDI-based injection via@Inject, these objects represent default validator and validatorfactory and thus can be configured using the XML descriptorMETA-INF/validation.xml (seeChapter 8,Configuring via XML).
When your application is CDI-enabled, the injected objects are CDI-aware as well and e.g. supportdependency injection in constraint validators.
Hibernate Validator also provides support for the unwrapping of JavaFX properties. If JavaFX is presenton the classpath,ValueExtractors for JavaFX properties are automatically registered.SeeSection 7.4, “JavaFX value extractors” for examples and further discussion.
In this chapter you will learn how to make use of several features provided by Hibernate Validatorin addition to the functionality defined by the Jakarta Bean Validation specification. This includes thefail fast mode, the API for programmatic constraint configuration and the boolean composition ofconstraints.
New APIs or SPIs are tagged with theorg.hibernate.validator.Incubating annotation as long as theyare under development. This means that such elements (e.g. packages, types, methods, constants etc.)may be incompatibly altered - or removed - in subsequent releases. Usage of incubating API/SPI membersis encouraged (so the development team can get feedback on these new features) but you should beprepared for updating code which is using them as needed when upgrading to a new version of HibernateValidator.
Using the features described in the following sections may result in application code which is notportable between Jakarta Bean Validation providers. |
Let’s start, however, with a look at the public API of Hibernate Validator. Below you can find a list of all packages belonging to this API and their purpose.Note that when a package is part of the public API this is not necessarily true for its sub-packages.
org.hibernate.validatorClasses used by the Jakarta Bean Validation bootstrap mechanism (eg. validation provider, configuration class); for more details seeChapter 9,Bootstrapping.
org.hibernate.validator.cfg,org.hibernate.validator.cfg.context,org.hibernate.validator.cfg.defs,org.hibernate.validator.spi.cfgHibernate Validator’s fluent API for constraint declaration; inorg.hibernate.validator.cfg you will find theConstraintMapping interface, inorg.hibernate.validator.cfg.defs all constraint definitions and inorg.hibernate.validator.spi.cfg a callback for using the API for configuring the default validator factory. Refer toSection 12.4, “Programmatic constraint definition and declaration” for the details.
org.hibernate.validator.constraints,org.hibernate.validator.constraints.br,org.hibernate.validator.constraints.plSome useful custom constraints provided by Hibernate Validator in addition to the built-in constraints defined by the Jakarta Bean Validation specification; the constraints are described in detail inSection 2.3.2, “Additional constraints”.
org.hibernate.validator.constraintvalidationExtended constraint validator context which allows to set custom attributes for message interpolation.Section 12.13.1, “HibernateConstraintValidatorContext” describes how to make use of that feature.
org.hibernate.validator.group,org.hibernate.validator.spi.groupThe group sequence provider feature which allows you to define dynamic default group sequences in function of the validated object state; the specifics can be found inSection 5.4, “Redefining the default group sequence”.
org.hibernate.validator.messageinterpolation,org.hibernate.validator.resourceloading,org.hibernate.validator.spi.resourceloadingClasses related to constraint message interpolation; the first package contains Hibernate Validator’s default message interpolator,ResourceBundleMessageInterpolator. The latter two packages provide theResourceBundleLocator SPI for the loading of resource bundles (seeSection 4.2.1, “ResourceBundleLocator”) and its default implementation.
org.hibernate.validator.parameternameproviderAParameterNameProvider based on the Paranamer library, seeSection 12.14, “Paranamer basedParameterNameProvider”.
org.hibernate.validator.propertypathExtensions to thejavax.validation.Path API, seeSection 12.7, “Extensions of the Path API”.
org.hibernate.validator.spi.constraintdefinitionAn SPI for registering additional constraint validators programmatically, seeSection 12.15, “Providing constraint definitions”.
org.hibernate.validator.spi.messageinterpolationAn SPI that can be used to tweak the resolution of the locale when interpolating the constraint violation messages. SeeSection 12.12, “Customizing the locale resolution”.
org.hibernate.validator.spi.nodenameproviderAn SPI that can be used to alter how the names of properties will be resolved when the property path is constructed. SeeSection 12.18, “Customizing the property name resolution for constraint violations”.
The public packages of Hibernate Validator fall into two categories: while the actual API parts areintended to beinvoked orused by clients (e.g. the API for programmatic constraint declarationor the custom constraints), the SPI (service provider interface) packages contain interfaces whichare intended to beimplemented by clients (e.g. |
Any packages not listed in that table are internal packages of Hibernate Validator and are notintended to be accessed by clients. The contents of these internal packages can change from releaseto release without notice, thus possibly breaking any client code relying on it.
Using the fail fast mode, Hibernate Validator allows to return from the current validation as soonas the first constraint violation occurs. This can be useful for the validation of large objectgraphs where you are only interested in a quick check whether there is any constraint violation atall.
Example 12.1, “Using the fail fast validation mode” shows how to bootstrap and use a fail fast enabled validator.
package org.hibernate.validator.referenceguide.chapter12.failfast;public class Car { @NotNull private String manufacturer; @AssertTrue private boolean isRegistered; public Car(String manufacturer, boolean isRegistered) { this.manufacturer = manufacturer; this.isRegistered = isRegistered; } //getters and setters...}Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .failFast( true ) .buildValidatorFactory() .getValidator();Car car = new Car( null, false );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );Here the validated object actually fails to satisfy both the constraints declared on theCar class,yet the validation call yields only oneConstraintViolation since the fail fast mode is enabled.
There is no guarantee in which order the constraints are evaluated, i.e. it is not deterministicwhether the returned violation originates from the |
Refer toSection 9.2.8, “Provider-specific settings” to learn about the different ways of enabling thefail fast mode when bootstrapping a validator.
The Jakarta Bean Validation specification defines a set of preconditions which apply when definingconstraints on methods within class hierarchies. These preconditions are defined insection 5.6.5of the Jakarta Bean Validation 2.0 specification. See alsoSection 3.1.4, “Method constraints in inheritance hierarchies”in this guide.
As per specification, a Jakarta Bean Validation provider is allowed to relax these preconditions.With Hibernate Validator you can do this in one of two ways.
First you can use the configuration propertieshibernate.validator.allow_parameter_constraint_override,hibernate.validator.allow_multiple_cascaded_validation_on_result andhibernate.validator.allow_parallel_method_parameter_constraint invalidation.xml. See exampleExample 12.2, “Configuring method validation behaviour in class hierarchies via properties”.
<?xml version="1.0" encoding="UTF-8"?><validation-config xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd" version="2.0"> <default-provider>org.hibernate.validator.HibernateValidator</default-provider> <property name="hibernate.validator.allow_parameter_constraint_override">true</property> <property name="hibernate.validator.allow_multiple_cascaded_validation_on_result">true</property> <property name="hibernate.validator.allow_parallel_method_parameter_constraint">true</property></validation-config>Alternatively these settings can be applied during programmatic bootstrapping.
HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();configuration.allowMultipleCascadedValidationOnReturnValues( true ) .allowOverridingMethodAlterParameterConstraint( true ) .allowParallelMethodsDefineParameterConstraints( true );By default, all of these properties are false, implementing the default behavior as defined in theJakarta Bean Validation specification.
Changing the default behaviour for method validation will result in non specification-conforming and nonportable application. Make sure to understand what you are doing and that your use case reallyrequires changes to the default behaviour. |
As per the Jakarta Bean Validation specification, you can define and declare constraints using Java annotations and XMLbased constraint mappings.
In addition, Hibernate Validator provides a fluent API which allows for the programmaticconfiguration of constraints. Use cases include the dynamic addition of constraints at runtimedepending on some application state or tests where you need entities with different constraints indifferent scenarios but don’t want to implement actual Java classes for each test case.
By default, constraints added via the fluent API are additive to constraints configured via thestandard configuration capabilities. But it is also possible to ignore annotation and XML configuredconstraints where required.
The API is centered around theConstraintMapping interface. You obtain a new mapping viaHibernateValidatorConfiguration#createConstraintMapping() which you then can configure in a fluentmanner as shown inExample 12.4, “Programmatic constraint declaration”.
HibernateValidatorConfiguration configuration = Validation .byProvider( HibernateValidator.class ) .configure();ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .field( "manufacturer" ) .constraint( new NotNullDef() ) .field( "licensePlate" ) .ignoreAnnotations( true ) .constraint( new NotNullDef() ) .constraint( new SizeDef().min( 2 ).max( 14 ) ) .type( RentalCar.class ) .getter( "rentalStation" ) .constraint( new NotNullDef() );Validator validator = configuration.addMapping( constraintMapping ) .buildValidatorFactory() .getValidator();Constraints can be configured on multiple classes and properties using method chaining. Theconstraint definition classesNotNullDef andSizeDef are helper classes which allow to configureconstraint parameters in a type-safe fashion. Definition classes exist for all built-in constraintsin theorg.hibernate.validator.cfg.defs package. By callingignoreAnnotations() any constraintsconfigured via annotations or XML are ignored for the given element.
Each element (type, property, method etc.) may only be configured once within all the constraintmappings used to set up one validator factory. Otherwise a |
It is not supported to add constraints to non-overridden supertype properties and methods byconfiguring a subtype. Instead you need to configure the supertype in this case. |
Having configured the mapping, you must add it back to the configuration object from which you thencan obtain a validator factory.
For custom constraints, you can either create your own definition classes extendingConstraintDef oryou can useGenericConstraintDef as seen inExample 12.5, “Programmatic declaration of a custom constraint”.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .field( "licensePlate" ) .constraint( new GenericConstraintDef<>( CheckCase.class ) .param( "value", CaseMode.UPPER ) );Container element constraints are supported by the programmatic API, usingcontainerElementType().
Example 12.6, “Programmatic declaration of a nested container element constraint” show an example where constraints are declared onnested container elements.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .field( "manufacturer" ) .constraint( new NotNullDef() ) .field( "licensePlate" ) .ignoreAnnotations( true ) .constraint( new NotNullDef() ) .constraint( new SizeDef().min( 2 ).max( 14 ) ) .field( "partManufacturers" ) .containerElementType( 0 ) .constraint( new NotNullDef() ) .containerElementType( 1, 0 ) .constraint( new NotNullDef() ) .type( RentalCar.class ) .getter( "rentalStation" ) .constraint( new NotNullDef() );As demonstrated, the parameters passed tocontainerElementType() are the path of type argumentindexes used to obtain the desired nested container element type.
By invokingvalid() you can mark a member for cascaded validation which is equivalent to annotatingit with@Valid. Configure any group conversions to be applied during cascaded validation using theconvertGroup() method (equivalent to@ConvertGroup). An example can be seen inExample 12.7, “Marking a property for cascaded validation”.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .field( "driver" ) .constraint( new NotNullDef() ) .valid() .convertGroup( Default.class ).to( PersonDefault.class ) .field( "partManufacturers" ) .containerElementType( 0 ) .valid() .containerElementType( 1, 0 ) .valid() .type( Person.class ) .field( "name" ) .constraint( new NotNullDef().groups( PersonDefault.class ) );You can not only configure bean constraints using the fluent API but also method and constructorconstraints. As shown inExample 12.8, “Programmatic declaration of method and constructor constraints” constructors are identified by theirparameter types and methods by their name and parameter types. Having selected a method orconstructor, you can mark its parameters and/or return value for cascaded validation and addconstraints as well as cross-parameter constraints.
As shown in the example,valid() can be also invoked on a container element type.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .constructor( String.class ) .parameter( 0 ) .constraint( new SizeDef().min( 3 ).max( 50 ) ) .returnValue() .valid() .method( "drive", int.class ) .parameter( 0 ) .constraint( new MaxDef().value( 75 ) ) .method( "load", List.class, List.class ) .crossParameter() .constraint( new GenericConstraintDef<>( LuggageCountMatchesPassengerCount.class ).param( "piecesOfLuggagePerPassenger", 2 ) ) .method( "getDriver" ) .returnValue() .constraint( new NotNullDef() ) .valid();Last but not least you can configure the default group sequence or the default group sequenceprovider of a type as shown in the following example.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .type( Car.class ) .defaultGroupSequence( Car.class, CarChecks.class ) .type( RentalCar.class ) .defaultGroupSequenceProviderClass( RentalCarGroupSequenceProvider.class );If you are not bootstrapping a validator factory manuallybut work with the default factory as configured viaMETA-INF/validation.xml(seeChapter 8,Configuring via XML),you can add one or more constraint mappings by creating one or several constraint mapping contributors.To do so, implement theConstraintMappingContributor contract:
ConstraintMappingContributor implementationpackage org.hibernate.validator.referenceguide.chapter12.constraintapi;public class MyConstraintMappingContributor implements ConstraintMappingContributor { @Override public void createConstraintMappings(ConstraintMappingBuilder builder) { builder.addConstraintMapping() .type( Marathon.class ) .getter( "name" ) .constraint( new NotNullDef() ) .field( "numberOfHelpers" ) .constraint( new MinDef().value( 1 ) ); builder.addConstraintMapping() .type( Runner.class ) .field( "paidEntryFee" ) .constraint( new AssertTrueDef() ); }}You then need to specify the fully-qualified class name of the contributor implementation inMETA-INF/validation.xml,using the property keyhibernate.validator.constraint_mapping_contributors. You can specify severalcontributors by separating them with a comma.
In case you specify a purely composed constraint - i.e. a constraint which has no validator itself but is solely madeup from other, composing constraints - on a method declaration, the validation engine cannot determine whether thatconstraint is to be applied as a return value constraint or as a cross-parameter constraint.
Hibernate Validator allows to resolve such ambiguities by specifying the@SupportedValidationTarget annotation on thedeclaration of the composed constraint type as shown inExample 12.11, “Specifying the validation target of a purely composed constraint”.The@ValidInvoiceAmount does not declare any validator, but it is solely composed by the@Min and@NotNullconstraints. The@SupportedValidationTarget ensures that the constraint is applied to the method return value whengiven on a method declaration.
package org.hibernate.validator.referenceguide.chapter12.purelycomposed;@Min(value = 0)@NotNull@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retention(RUNTIME)@Documented@Constraint(validatedBy = {})@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)@ReportAsSingleViolationpublic @interface ValidInvoiceAmount { String message() default "{org.hibernate.validator.referenceguide.chapter11.purelycomposed." + "ValidInvoiceAmount.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @OverridesAttribute(constraint = Min.class, name = "value") long value();}Jakarta Bean Validation specifies that the constraints of a composed constraint (seeSection 6.4, “Constraint composition”) are all combined via a logicalAND. This means all of thecomposing constraints need to return true to obtain an overall successful validation.
Hibernate Validator offers an extension to this and allows you to compose constraints via a logicalOR orNOT. To do so, you have to use the ConstraintComposition annotation and the enumCompositionType with its valuesAND,OR andALL_FALSE.
Example 12.12, “OR composition of constraints” shows how to build a composed constraint@PatternOrSizewhere only one of the composing constraints needs to be valid in order to pass the validation.Either the validated string is all lower-cased or it is between two and three characters long.
package org.hibernate.validator.referenceguide.chapter12.booleancomposition;@ConstraintComposition(OR)@Pattern(regexp = "[a-z]")@Size(min = 2, max = 3)@ReportAsSingleViolation@Target({ METHOD, FIELD })@Retention(RUNTIME)@Constraint(validatedBy = { })public @interface PatternOrSize { String message() default "{org.hibernate.validator.referenceguide.chapter11." + "booleancomposition.PatternOrSize.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };}UsingALL_FALSE as composition type implicitly enforces that only a single violation will getreported in case validation of the constraint composition fails. |
Hibernate Validator provides an extension to thejavax.validation.Path API.For nodes ofElementKind.PROPERTY andElementKind.CONTAINER_ELEMENT it allows to obtain the value of therepresented property.To do so, narrow down a given node to the typeorg.hibernate.validator.path.PropertyNode ororg.hibernate.validator.path.ContainerElementNode respectively usingNode#as(), asshown in the following example:
Building building = new Building();// Assume the name of the person violates a @Size constraintPerson bob = new Person( "Bob" );Apartment bobsApartment = new Apartment( bob );building.getApartments().add( bobsApartment );Set<ConstraintViolation<Building>> constraintViolations = validator.validate( building );Path path = constraintViolations.iterator().next().getPropertyPath();Iterator<Path.Node> nodeIterator = path.iterator();Path.Node node = nodeIterator.next();assertEquals( node.getName(), "apartments" );assertSame( node.as( PropertyNode.class ).getValue(), bobsApartment );node = nodeIterator.next();assertEquals( node.getName(), "resident" );assertSame( node.as( PropertyNode.class ).getValue(), bob );node = nodeIterator.next();assertEquals( node.getName(), "name" );assertEquals( node.as( PropertyNode.class ).getValue(), "Bob" );This is also very useful to obtain the element ofSet properties on the property path (e.g.apartmentsin the example) which otherwise could not be identified (unlike forMap andList, there is no key nor indexin this case).
ConstraintViolationIn some cases automatic processing of violations can be aided, if the constraint violation provides additionaldata - a so called dynamic payload. This dynamic payload could for example contain hints to the user on how toresolve the violation.
Dynamic payloads can be set incustom constraints usingHibernateConstraintValidatorContext.This is shown in exampleExample 12.14, “ConstraintValidator implementation setting a dynamic payload” where thejavax.validation.ConstraintValidatorContext is unwrapped toHibernateConstraintValidatorContext in order to callwithDynamicPayload.
ConstraintValidator implementation setting a dynamic payloadpackage org.hibernate.validator.referenceguide.chapter12.dynamicpayload;import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> { private static final Map<Integer, String> suggestedCars = newHashMap(); static { suggestedCars.put( 2, "Chevrolet Corvette" ); suggestedCars.put( 3, "Toyota Volta" ); suggestedCars.put( 4, "Maserati GranCabrio" ); suggestedCars.put( 5, " Mercedes-Benz E-Class" ); } @Override public void initialize(ValidPassengerCount constraintAnnotation) { } @Override public boolean isValid(Car car, ConstraintValidatorContext context) { if ( car == null ) { return true; } int passengerCount = car.getPassengers().size(); if ( car.getSeatCount() >= passengerCount ) { return true; } else { if ( suggestedCars.containsKey( passengerCount ) ) { HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); hibernateContext.withDynamicPayload( suggestedCars.get( passengerCount ) ); } return false; } }}On the constraint violation processing side, ajavax.validation.ConstraintViolation can then in turn beunwrapped toHibernateConstraintViolation in order to retrieve the dynamic payload for further processing.
ConstraintViolation's dynamic payloadCar car = new Car( 2 );car.addPassenger( new Person() );car.addPassenger( new Person() );car.addPassenger( new Person() );Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );assertEquals( 1, constraintViolations.size() );ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();@SuppressWarnings("unchecked")HibernateConstraintViolation<Car> hibernateConstraintViolation = constraintViolation.unwrap( HibernateConstraintViolation.class);String suggestedCar = hibernateConstraintViolation.getDynamicPayload( String.class );assertEquals( "Toyota Volta", suggestedCar );Hibernate Validator restricts the Expression Language features exposed by default.
For this purpose, we define several feature levels inExpressionLanguageFeatureLevel:
NONE: Expression Language interpolation is fully disabled.
VARIABLES: Allow interpolation of the variables injected viaaddExpressionVariable(), resources bundles and usage of theformatter object.
BEAN_PROPERTIES: Allow everythingVARIABLES allows plus the interpolation of bean properties.
BEAN_METHODS: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.
Depending on the context, the features we expose are different:
For constraints, the default level isBEAN_PROPERTIES.For all the built-in constraint messages to be correctly interpolated, you need at least theVARIABLES level.
For custom violations, created via theConstraintValidatorContext, Expression Language is disabled by default.You can enable it for specific custom violations and, when enabled, it will default toVARIABLES.
Hibernate Validator provides ways to override these defaults when boostrapping theValidatorFactory.
To change the Expression Language feature level for constraints, use the following:
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .constraintExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES ) .buildValidatorFactory();To change the Expression Language feature level for custom violations, use the following:
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .customViolationExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES ) .buildValidatorFactory();Doing this will automatically enable Expression Language for all the custom violations in your application. It should only be used for compatibility and to ease the migration from older Hibernate Validator versions. |
These levels can also be defined using the following properties:
hibernate.validator.constraint_expression_language_feature_level
hibernate.validator.custom_violation_expression_language_feature_level
Accepted values for these properties are:none,variables,bean-properties andbean-methods.
ParameterMessageInterpolatorHibernate Validator requires per default an implementation of the Unified EL (seeSection 1.1.1, “Unified EL”) to be available. This is needed to allow the interpolationof constraint error messages using EL expressions as defined by the Jakarta Bean Validation specification.
For environments where you cannot or do not want to provide an EL implementation, Hibernate Validatoroffers a non EL based message interpolator -org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator.
Refer toSection 4.2, “Custom message interpolation” to see how to plug in custom message interpolatorimplementations.
Constraint messages containing EL expressions will be returned un-interpolated by |
ResourceBundleLocatorWithResourceBundleLocator, Hibernate Validator provides an additional SPI which allows to retrieveerror messages from other resource bundles thanValidationMessages while still using the actualinterpolation algorithm as defined by the specification. Refer toSection 4.2.1, “ResourceBundleLocator” to learn how to make use of that SPI.
These contracts are marked as |
Hibernate Validator provides several extension points to build a custom locale resolution strategy.The resolved locale is used when interpolating the constraint violation messages.
The default behavior of Hibernate Validator is to always use the system default locale (as obtained viaLocale.getDefault()).This might not be the desired behavior if, for example, you usually set your system locale toen-US but want your application to provide messages in French.
The following example shows how to set the Hibernate Validator default locale tofr-FR:
Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .defaultLocale( Locale.FRANCE ) .buildValidatorFactory() .getValidator();Set<ConstraintViolation<Bean>> violations = validator.validate( new Bean() );assertEquals( "doit avoir la valeur vrai", violations.iterator().next().getMessage() );While this is already a nice improvement, in a fully internationalized application, this is not sufficient:you need Hibernate Validator to select the locale depending on the user context.
Hibernate Validator provides theorg.hibernate.validator.spi.messageinterpolation.LocaleResolver SPIwhich allows to fine-tune the resolution of the locale.Typically, in a JAX-RS environment, you can resolve the locale to use from theAccept-Language HTTP header.
In the following example, we use a hardcoded value but, for instance, in the case of a RESTEasy application,you could extract the header from theResteasyContext.
LocaleResolverLocaleResolver localeResolver = new LocaleResolver() { @Override public Locale resolve(LocaleResolverContext context) { // get the locales supported by the client from the Accept-Language header String acceptLanguageHeader = "it-IT;q=0.9,en-US;q=0.7"; List<LanguageRange> acceptedLanguages = LanguageRange.parse( acceptLanguageHeader ); List<Locale> resolvedLocales = Locale.filter( acceptedLanguages, context.getSupportedLocales() ); if ( resolvedLocales.size() > 0 ) { return resolvedLocales.get( 0 ); } return context.getDefaultLocale(); }};Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .defaultLocale( Locale.FRANCE ) .locales( Locale.FRANCE, Locale.ITALY, Locale.US ) .localeResolver( localeResolver ) .buildValidatorFactory() .getValidator();Set<ConstraintViolation<Bean>> violations = validator.validate( new Bean() );assertEquals( "deve essere true", violations.iterator().next().getMessage() );When using the |
The Jakarta Bean Validation specification offers at several points in its API the possibility to unwrap agiven interface to an implementor specific subtype. In the case of constraint violation creation inConstraintValidator implementations as well as message interpolation inMessageInterpolatorinstances, there existunwrap() methods for the provided context instances -ConstraintValidatorContext respectivelyMessageInterpolatorContext. Hibernate Validator providescustom extensions for both of these interfaces.
HibernateConstraintValidatorContextHibernateConstraintValidatorContext is a subtype ofConstraintValidatorContext which allows you to:
enable Expression Language interpolation for a particular custom violation - see below
set arbitrary parameters for interpolation via the Expression Language message interpolationfacility usingHibernateConstraintValidatorContext#addExpressionVariable(String, Object)orHibernateConstraintValidatorContext#addMessageParameter(String, Object).
@Future validator injecting an expression variablepackage org.hibernate.validator.referenceguide.chapter12.context;public class MyFutureValidator implements ConstraintValidator<Future, Instant> { @Override public void initialize(Future constraintAnnotation) { } @Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; } HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); Instant now = Instant.now( context.getClockProvider().getClock() ); if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addExpressionVariable( "now", now ) .buildConstraintViolationWithTemplate( "Must be after ${now}" ) .addConstraintViolation(); return false; } return true; }}@Future validator injecting a message parameterpackage org.hibernate.validator.referenceguide.chapter12.context;public class MyFutureValidatorMessageParameter implements ConstraintValidator<Future, Instant> { @Override public void initialize(Future constraintAnnotation) { } @Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; } HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); Instant now = Instant.now( context.getClockProvider().getClock() ); if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addMessageParameter( "now", now ) .buildConstraintViolationWithTemplate( "Must be after {now}" ) .addConstraintViolation(); return false; } return true; }}Apart from the syntax, the main difference between message parameters and expression variables is that message parametersare simply interpolated whereas expression variables are interpreted using the Expression Language engine.In practice, use message parameters if you do not need the advanced features of an Expression Language. |
Note that the parameters specified via |
set an arbitrary dynamic payload - seeSection 12.8, “Dynamic payload as part ofConstraintViolation”
By default, Expression Language interpolation isdisabled for custom violations,this to avoid arbitrary code execution or sensitive data leak if message templates are built from improperly escaped user input.
It is possible to enable Expression Language for a given custom violation by usingenableExpressionLanguage() as shown in the example below:
public class SafeValidator implements ConstraintValidator<ZipCode, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if ( value == null ) { return true; } HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); hibernateContext.disableDefaultConstraintViolation(); if ( isInvalid( value ) ) { hibernateContext .addExpressionVariable( "validatedValue", value ) .buildConstraintViolationWithTemplate( "${validatedValue} is not a valid ZIP code" ) .enableExpressionLanguage() .addConstraintViolation(); return false; } return true; } private boolean isInvalid(String value) { // ... return false; }}In this case, the message template will be interpolated by the Expression Language engine.
By default, only variables interpolation is enabled when enabling Expression Language.
You can enable more features by usingHibernateConstraintViolationBuilder#enableExpressionLanguage(ExpressionLanguageFeatureLevel level).
We define several levels of features for Expression Language interpolation:
NONE: Expression Language interpolation is fully disabled - this is the default for custom violations.
VARIABLES: Allow interpolation of the variables injected viaaddExpressionVariable(), resources bundles and usage of theformatter object.
BEAN_PROPERTIES: Allow everythingVARIABLES allows plus the interpolation of bean properties.
BEAN_METHODS: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.
Using If you inject user input by simply concatenating the user input in the message,you will allow potential arbitrary code execution and sensitive data leak:if the user input contains valid expressions, they will be executed by the Expression Language engine. Here is an example of something you shouldABSOLUTELY NOT do: In the example above, if |
HibernateMessageInterpolatorContextHibernate Validator also offers a custom extension ofMessageInterpolatorContext, namelyHibernateMessageInterpolatorContext (seeExample 12.18, “HibernateMessageInterpolatorContext”). Thissubtype was introduced to allow a better integration of Hibernate Validator into Glassfish. Theroot bean type was in this case needed to determine the right class loader for the message resourcebundle. If you have any other use cases, let us know.
HibernateMessageInterpolatorContextpublic interface HibernateMessageInterpolatorContext extends MessageInterpolator.Context { /** * Returns the currently validated root bean type. * * @return The currently validated root bean type. */ Class<?> getRootBeanType(); /** * @return the message parameters added to this context for interpolation * * @since 5.4.1 */ Map<String, Object> getMessageParameters(); /** * @return the expression variables added to this context for EL interpolation * * @since 5.4.1 */ Map<String, Object> getExpressionVariables(); /** * @return the path to the validated constraint starting from the root bean * * @since 6.1 */ Path getPropertyPath(); /** * @return the level of features enabled for the Expression Language engine * * @since 6.2 */ ExpressionLanguageFeatureLevel getExpressionLanguageFeatureLevel();}ParameterNameProviderHibernate Validator comes with aParameterNameProvider implementation which leverages theParanamer library.
This library provides several ways for obtaining parameter names at runtime, e.g. based on debugsymbols created by the Java compiler, constants with the parameter names woven into the bytecode ina post-compile step or annotations such as the@Named annotation from JSR 330.
In order to useParanamerParameterNameProvider, either pass an instance when bootstrapping avalidator as shown inExample 9.10, “Using a customParameterNameProvider” or specifyorg.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider as value for the<parameter-name-provider> element in theMETA-INF/validation.xml file.
When using this parameter name provider, you need to add the Paranamer library to your classpath. Itis available in the Maven Central repository with the group id |
By defaultParanamerParameterNameProvider retrieves parameter names from constants added to the bytecode at build time (viaDefaultParanamer) and debug symbols (viaBytecodeReadingParanamer).Alternatively you can specify aParanamer implementation of your choice when creating aParanamerParameterNameProvider instance.
Jakarta Bean Validation allows to (re-)define constraint definitions via XML in its constraint mappingfiles. SeeSection 8.2, “Mapping constraints viaconstraint-mappings” for more information andExample 8.2, “Bean constraints configured via XML”for an example. While this approach is sufficient for many use cases, it has its shortcomingsin others. Imagine for example a constraint library wanting to contribute constraintdefinitions for custom types. This library could provide a mapping file with their library, but thisfile still would need to be referenced by the user of the library. Luckily there are better ways.
The following concepts are considered experimental at this time. Let us know whether you find themuseful and whether they meet your needs. |
ServiceLoaderHibernate Validator allows to utilize Java’sServiceLoadermechanism to register additional constraint definitions. All you have to do is to add the filejavax.validation.ConstraintValidator toMETA-INF/services. In this service file you list thefully qualified classnames of your constraint validator classes (one per line). Hibernate Validatorwill automatically infer the constraint types they apply to.SeeConstraint definition via service filefor an example.
# Assuming a custom constraint annotation @org.mycompany.CheckCaseorg.mycompany.CheckCaseValidatorTo contribute default messages for your custom constraints, place a fileContributorValidationMessages.propertiesand/or its locale-specific specializations at the root of your JAR. Hibernate Validator will consider theentries from all the bundles with this name found on the classpath in addition to those given inValidationMessages.properties.
This mechanism is also helpful when creating large multi-module applications: instead of putting all the constraintmessages into one single bundle, you can have one resource bundle per module containing only those messages of that module.
We highly recommend the reading ofthis blog post by Marko Bekhta,guiding you step by step through the process of creating an independent JAR that contains your custom constraintsand declares them via the |
While the service loader approach works in many scenarios, but not in all (think for exampleOSGi where service files are not visible), there is yet another way of contributing constraintdefinitions. You can use the programmatic constraint declaration API - seeExample 12.20, “Adding constraint definitions through the programmatic API”.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .constraintDefinition( ValidPassengerCount.class ) .validatedBy( ValidPassengerCountValidator.class );If your validator implementation is rather simple (i.e. no initialization from the annotation is needed,andConstraintValidatorContext is not used), you also can use this alternative API to specify the constraint logic using a Lambda expression or method reference:
ConstraintMapping constraintMapping = configuration.createConstraintMapping();constraintMapping .constraintDefinition( ValidPassengerCount.class ) .validateType( Bus.class ) .with( b -> b.getSeatCount() >= b.getPassengers().size() );Instead of directly adding a constraint mapping to the configuration object, you may use aConstraintMappingContributoras detailed inSection 12.5, “Applying programmatic constraint declarations to the default validator factory”. This can be useful whenconfiguring the default validator factory usingMETA-INF/validation.xml (seeChapter 8,Configuring via XML).
One use case for registering constraint definitions through the programmatic API is the ability to specify an alternativeconstraint validator for the Using the programmatic constraint declaration API to register a regular expression based constraint definition for @URL |
There are several cases in which Hibernate Validator needs to load resources or classes given by name:
XML descriptors (META-INF/validation.xml as well as XML constraint mappings)
classes specified by name in XML descriptors (e.g. custom message interpolators etc.)
theValidationMessages resource bundle
theExpressionFactory implementation used for expression based message interpolation
By default, Hibernate Validator tries to load these resources via the current thread context class loader.If that’s not successful, Hibernate Validator’s own class loader will be tried as a fallback.
For cases where this strategy is not appropriate (e.g. modularized environments such as OSGi),you may provide a specific class loader for loading these resources when bootstrapping the validator factory:
Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .externalClassLoader( classLoader ) .buildValidatorFactory() .getValidator();In the case of OSGi, you could e.g. pass the loader of a class from the bundle bootstrapping Hibernate Validatoror a custom class loader implementation which delegates toBundle#loadClass() etc.
Call |
When a bean is validated by Hibernate Validator, its properties get validated. A property can eitherbe a field or a getter.By default, Hibernate Validator respects the JavaBeans specification and considers a method as a getter as soonas one of the conditions below is true:
the method name starts withget, it has a non-void return type and has no parameters;
the method name starts withis, has a return type ofboolean and has no parameters;
the method name starts withhas, has a return type ofboolean and has no parameters (this ruleis specific to Hibernate Validator and is not mandated by the JavaBeans specification)
While these rules are usually appropriate when following the classic JavaBeans convention, it might happen,especially with code generators, that the JavaBeans naming convention is not followed and that the getters'names are following a different convention.
In this case, the strategy for detecting getters should be redefined in order to fully validate the object.
A classic example of this requirement is when the classes follow a fluent naming convention,as illustrated inExample 12.23, “A class that uses non-standard getters”.
package org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy;public class User { private String firstName; private String lastName; private String email; // [...] @NotEmpty public String firstName() { return firstName; } @NotEmpty public String lastName() { return lastName; } @Email public String email() { return email; }}If such object gets validated, no validation will be performed on the getters as they are not detectedby the standard strategy.
Validator validator = Validation.byProvider( HibernateValidator.class ) .configure() .buildValidatorFactory() .getValidator();User user = new User( "", "", "not an email" );Set<ConstraintViolation<User>> constraintViolations = validator.validate( user );// as User has non-standard getters no violations are triggeredassertEquals( 0, constraintViolations.size() );To make Hibernate Validator treat such methods as properties, a customGetterPropertySelectionStrategyshould be configured.In this particular case, a possible implementation of the strategy would be:
GetterPropertySelectionStrategy implementationpackage org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy;public class FluentGetterPropertySelectionStrategy implements GetterPropertySelectionStrategy { private final Set<String> methodNamesToIgnore; public FluentGetterPropertySelectionStrategy() { // we will ignore all the method names coming from Object this.methodNamesToIgnore = Arrays.stream( Object.class.getDeclaredMethods() ) .map( Method::getName ) .collect( Collectors.toSet() ); } @Override public Optional<String> getProperty(ConstrainableExecutable executable) { if ( methodNamesToIgnore.contains( executable.getName() ) || executable.getReturnType() == void.class || executable.getParameterTypes().length > 0 ) { return Optional.empty(); } return Optional.of( executable.getName() ); } @Override public Set<String> getGetterMethodNameCandidates(String propertyName) { // As method name == property name, there always is just one possible name for a method return Collections.singleton( propertyName ); }}There are multiple ways to configure Hibernate Validator to use this strategy. It can either be doneprogrammatically (seeExample 12.26, “Configuring a customGetterPropertySelectionStrategy programmatically”) or by using thehibernate.validator.getter_property_selection_strategy property in the XML configuration(seeExample 12.27, “Configuring a customGetterPropertySelectionStrategy using an XML property”).
GetterPropertySelectionStrategy programmaticallyValidator validator = Validation.byProvider( HibernateValidator.class ) .configure() // Setting a custom getter property selection strategy .getterPropertySelectionStrategy( new FluentGetterPropertySelectionStrategy() ) .buildValidatorFactory() .getValidator();User user = new User( "", "", "not an email" );Set<ConstraintViolation<User>> constraintViolations = validator.validate( user );assertEquals( 3, constraintViolations.size() );GetterPropertySelectionStrategy using an XML property<validation-config xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd" version="2.0"> <property name="hibernate.validator.getter_property_selection_strategy"> org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy.NoPrefixGetterPropertySelectionStrategy </property></validation-config>It is important to mention that in cases where programmatic constraints are added using |
Imagine that we have a simple data class that has@NotNull constraints on some fields:
public class Person { @NotNull @JsonProperty("first_name") private final String firstName; @JsonProperty("last_name") private final String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }}This class can be serialized to JSON by using theJackson library:
public class PersonSerializationTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Test public void personIsSerialized() throws JsonProcessingException { Person person = new Person( "Clark", "Kent" ); String serializedPerson = objectMapper.writeValueAsString( person ); assertEquals( "{\"first_name\":\"Clark\",\"last_name\":\"Kent\"}", serializedPerson ); }}As we can see, the object is serialized to:
{ "first_name": "Clark", "last_name": "Kent"}Notice how the names of the properties differ. In the Java object, we havefirstName andlastName, whereas in the JSON output, we havefirst_name andlast_name.We customized this behavior through@JsonProperty annotations.
Now imagine that we use this class in a REST environment, where a user can sendaPerson instance as JSON in the request body.It would be nice, when indicating on which field the validation failed, to indicate the name they use in their JSON request,first_name,and not the name we use internally in our Java code,firstName.
Theorg.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider contract allows us to do this.By implementing it, we can define how the name of a property will be resolved during validation.In our case, we want to read the value from the Jackson configuration.
One example of how to do this is to leverage the Jackson API:
import org.hibernate.validator.spi.nodenameprovider.JavaBeanProperty;import org.hibernate.validator.spi.nodenameprovider.Property;import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider;import com.fasterxml.jackson.databind.BeanDescription;import com.fasterxml.jackson.databind.JavaType;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;public class JacksonPropertyNodeNameProvider implements PropertyNodeNameProvider { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String getName(Property property) { if ( property instanceof JavaBeanProperty ) { return getJavaBeanPropertyName( (JavaBeanProperty) property ); } return getDefaultName( property ); } private String getJavaBeanPropertyName(JavaBeanProperty property) { JavaType type = objectMapper.constructType( property.getDeclaringClass() ); BeanDescription desc = objectMapper.getSerializationConfig().introspect( type ); return desc.findProperties() .stream() .filter( prop -> prop.getInternalName().equals( property.getName() ) ) .map( BeanPropertyDefinition::getName ) .findFirst() .orElse( property.getName() ); } private String getDefaultName(Property property) { return property.getName(); }}And when doing the validation:
public class JacksonPropertyNodeNameProviderTest { @Test public void nameIsReadFromJacksonAnnotationOnField() { ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .propertyNodeNameProvider( new JacksonPropertyNodeNameProvider() ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); Person clarkKent = new Person( null, "Kent" ); Set<ConstraintViolation<Person>> violations = validator.validate( clarkKent ); ConstraintViolation<Person> violation = violations.iterator().next(); assertEquals( violation.getPropertyPath().toString(), "first_name" ); }We can see that the property path now returnsfirst_name.
Note that this also works when the annotations are on a getter:
@Testpublic void nameIsReadFromJacksonAnnotationOnGetter() { ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .propertyNodeNameProvider( new JacksonPropertyNodeNameProvider() ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); Person clarkKent = new Person( null, "Kent" ); Set<ConstraintViolation<Person>> violations = validator.validate( clarkKent ); ConstraintViolation<Person> violation = violations.iterator().next(); assertEquals( violation.getPropertyPath().toString(), "first_name" );}public class Person { private final String firstName; @JsonProperty("last_name") private final String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @NotNull @JsonProperty("first_name") public String getFirstName() { return firstName; }}This is just one use case of why we would like to change how the property names are resolved.
org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider can be implemented to provide a property name inwhatever way you see fit (reading from annotations, for instance).
There are two more interfaces that are worth mentioning:
org.hibernate.validator.spi.nodenameprovider.Property is a base interface that holds metadata about a property. Ithas a singleString getName() method that can be used to get the "original" name of a property. This interfaceshould be used as a default way of resolving the name (see how it is used inExample 12.31, “JacksonPropertyNodeNameProvider implementation”).
org.hibernate.validator.spi.nodenameprovider.JavaBeanProperty is an interface that holds metadata about a bean property. Itextendsorg.hibernate.validator.spi.nodenameprovider.Property and provide some additional methods likeClass<?> getDeclaringClass()which returns the class that is the owner of the property.
Have you ever caught yourself by unintentionally doing things like
specifying constraint annotations at unsupported data types (e.g. by annotating a String with@Past)
annotating the setter of a JavaBeans property (instead of the getter method)
annotating static fields/methods with constraint annotations (which is not supported)?
Then the Hibernate Validator Annotation Processor is the right thing for you. It helps preventingsuch mistakes by plugging into the build process and raising compilation errors whenever constraintannotations are incorrectly used.
You can find the Hibernate Validator Annotation Processor as part of the distribution bundle onSourceforge or in theusual Maven repositories such as Maven Central under the GAV |
The Hibernate Validator Annotation Processor is based on the "Pluggable Annotation Processing API"as defined byJSR 269 which is part of the JavaPlatform.
As of Hibernate Validator 6.2.5.Final the Hibernate Validator Annotation Processor checks that:
constraint annotations are allowed for the type of the annotated element
only non-static fields or methods are annotated with constraint annotations
only non-primitive fields or methods are annotated with@Valid
only such methods are annotated with constraint annotations which are valid JavaBeansgetter methods (optionally, see below)
only such annotation types are annotated with constraint annotations which are constraintannotations themselves
definition of dynamic default group sequence with@GroupSequenceProvider is valid
annotation parameter values are meaningful and valid
method parameter constraints in inheritance hierarchies respect the inheritance rules
method return value constraints in inheritance hierarchies respect the inheritance rules
The behavior of the Hibernate Validator Annotation Processor can be controlled using the followingprocessor options:
diagnosticKindControls how constraint problems are reported. Must be thestring representation of one of the values from the enumjavax.tools.Diagnostic.Kind,e.g.WARNING. A value ofERROR will cause compilation to halt whenever the AP detectsa constraint problem. Defaults toERROR.
methodConstraintsSupportedControls whether constraints are allowed at methods of anykind. Must be set totrue when working with method level constraints as supported byHibernate Validator. Can be set tofalse to allow constraints only atJavaBeans getter methods as defined by the Jakarta Bean Validation API. Defaults totrue.
verboseControls whether detailed processing information shall bedisplayed or not, useful for debugging purposes. Must be eithertrue orfalse. Defaults tofalse.
This section shows in detail how to integrate the Hibernate Validator Annotation Processor intocommand line builds (Maven, Ant, javac) as well as IDE-based builds (Eclipse, IntelliJ IDEA,NetBeans).
For using the Hibernate Validator annotation processor with Maven, set it up via theannotationProcessorPaths option like this:
<project> [...] <build> [...] <plugins> [...] <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator-annotation-processor</artifactId> <version>6.2.5.Final</version> </path> </annotationProcessorPaths> </configuration> </plugin> [...] </plugins> [...] </build> [...]</project>When usingGradle it is enough to reference the annotation processor as anannotationProcessor dependency.
dependencies { annotationProcessor group: 'org.hibernate.validator', name: 'hibernate-validator-annotation-processor', version: '6.2.5.Final' // any other dependencies ...}Similar to directly working with javac, the annotation processor can be added as as compilerargument when invoking thejavac taskforApache Ant:
<javac srcdir="src/main" destdir="build/classes" classpath="/path/to/validation-api-2.0.2.jar"> <compilerarg value="-processorpath" /> <compilerarg value="/path/to/hibernate-validator-annotation-processor-6.2.5.Final.jar"/></javac>When compiling on the command line usingjavac, specify the JARhibernate-validator-annotation-processor-6.2.5.Final.jar using the "processorpath" option as shown inthe following listing. The processor will be detected automatically by the compiler and invokedduring compilation.
javac src/main/java/org/hibernate/validator/ap/demo/Car.java \ -cp /path/to/validation-api-2.0.2.jar \ -processorpath /path/to/hibernate-validator-annotation-processor-6.2.5.Final.jar
The annotation processor will automatically be set up for Maven projects configured as described above,provided you have theM2E Eclipse plug-in installed.
For plain Eclipse projects follow these steps to set up the annotation processor:
Right-click your project, choose "Properties"
Go to "Java Compiler" and make sure, that "Compiler compliance level" is set to "1.8".Otherwise the processor won’t be activated
Go to "Java Compiler - Annotation Processing" and choose "Enable annotation processing"
Go to "Java Compiler - Annotation Processing - Factory Path" and add the JARhibernate-validator-annotation-processor-6.2.5.Final.jar
Confirm the workspace rebuild
You now should see any annotation problems as regular error markers within the editor and in the"Problem" view:

The following steps must be followed to use the annotation processor withinIntelliJ IDEA (version 9 and above):
Go to "File", then "Settings",
Expand the node "Compiler", then "Annotation Processors"
Choose "Enable annotation processing" and enter the following as "Processor path":/path/to/hibernate-validator-annotation-processor-6.2.5.Final.jar
Add the processor’s fully qualified name org.hibernate.validator.ap.ConstraintValidationProcessorto the "Annotation Processors" list
If applicable add you module to the "Processed Modules" list
Rebuilding your project then should show any erroneous constraint annotations:

TheNetBeans IDE supports usingannotation processors within the IDE build. To do so, do the following:
Right-click your project, choose "Properties"
Go to "Libraries", tab "Processor", and add the JAR hibernate-validator-annotation-processor-6.2.5.Final.jar
Go to "Build - Compiling", select "Enable Annotation Processing" and "Enable Annotation Processingin Editor". Add the annotation processor by specifying its fully qualified nameorg.hibernate.validator.ap.ConstraintValidationProcessor
Any constraint annotation problems will then be marked directly within the editor:

The following known issues exist as of July 2017:
Container element constraints are not supported for now.
Constraints applied to a container but in reality applied to the container elements (be it viatheUnwrapping.Unwrap payload or via a value extractor marked with@UnwrapByDefault) are not supportedcorrectly.
HV-308: Additional validatorsregistered for a constraintusing XML arenot evaluated by the annotation processor.
Sometimes custom constraints can’t beproperly evaluated whenusing the processor within Eclipse. Cleaning the project can help in these situations. This seems tobe an issue with the Eclipse JSR 269 API implementation, but further investigation is required here.
When using the processor within Eclipse, the check of dynamic default group sequence definitionsdoesn’t work. After further investigation, it seems to be an issue with the Eclipse JSR 269 APIimplementation.
Last but not least, a few pointers to further information.
A great source for examples is the Jakarta Bean Validation TCK which is available for anonymous access onGitHub. In particular the TCK’stests might beof interest.The Jakarta Bean Validation specification itselfis also a great way to deepen your understanding of Jakarta Bean Validation and Hibernate Validator.
If you have any further questions about Hibernate Validator or want to share some of your use cases,have a look at theHibernate ValidatorWiki, theHibernate Validator Forum and theHibernate Validator tag on Stack Overflow.
In case you would like to report a bug useHibernate’s Jira instance.Feedback is always welcome!