Hibernate.orgCommunity Documentation
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. That way Bean Validation constraints can be used to specify
the preconditions that must be satisfied by the caller before a method or constructor may be invoked (by applying constraints to the parameters of an executable)
the postconditions that are guaranteed to the caller after a method or constructor invocation returns (by applying constraints to the return value of an executable)
For the purpose of this reference guide, the term method constraint refers to both, method and constructor constraints, if not stated otherwise. Ocassionally, the term executable is used when referering to methods and constructors.
This approach has several advantages over traditional ways of checking the correctness of parameters and return values:
the checks don't have to be performed manually (e.g. by throwing
IllegalArgumentExceptions
or similar), 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. This avoids redundancies and reduces the chance of inconsistencies between implementation and documentation
In order to make annotations show up in the JavaDoc of annoted
elements, the annotation types themselves must be annotated with the meta
annotation @Documented
. This is the case for all
built-in constraints 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 value constraints and how to validate them using the
ExecutableValidator
API.
You specify the preconditions of a method or constructor by adding constraint annotations to its parameters as demonstrated in Example 3.1, “Declaring method and constructor parameter constraints”.
Example 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:
The name passed to the RentalCar
constructor must not be null
When invoking the rentCar()
method,
the given customer must not be null
, the rental's
start date must not be null
and must be in the
future and the rental duration must be at least one day
Note that declaring method or constructor constraints itself does not automatically cause their validation upon invocation of the executable. Instead, the ExecutableValidator API (see Section 3.2, “Validating method constraints”) must be used to perform the validation, which is often 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 is not 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 as target of interception. Refer to the documentation of the interception technology to find out whether any such limitations exist.
Sometimes validation does not only depend on a single parameter but on several or even all parameters of a method or constructor. This kind of requirement can be fulfilled with help of a cross-parameter constraint.
Cross-parameter constraints can be considered as the method validation equivalent to class-level constraints. Both can be used to implement validation requirements which are based on several elements. While class-level constraints apply to several properties of a bean, cross-parameter constraints apply to several parameters of an executable.
In contrast to single-parameter constraints, cross-parameter
constraints are declared on the method or constructor as you can see
in Example 3.2, “Declaring a cross-parameter constraint”. Here
the cross-parameter constraint
@LuggageCountMatchesPassengerCount
declared on
the load()
method is used to ensure that no
passenger has more than two pieces of luggage.
Example 3.2. Declaring a cross-parameter constraint
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 method level. In order to distinguish
cross-parameter constraints from return value constraints, the
constraint target is configured in the
ConstraintValidator
implementation using the
@SupportedValidationTarget
annotation. You can
find out about the details in Section 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 constraints which allow to specify validation rules using expression or script languages.
Such constraints must define a member
validationAppliesTo()
which can be used at
declaration time to specify the constraint target. As shown in Example 3.3, “Specifying a constraint's target” you apply the
constraint to an executable's parameters by specifying
validationAppliesTo = ConstraintTarget.PARAMETERS
,
while ConstraintTarget.RETURN_VALUE
is used to
apply the constraint to the executable return value.
Example 3.3. Specifying a constraint's target
package org.hibernate.validator.referenceguide.chapter03.crossparameter.constrainttarget;
public class Garage {
@ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.PARAMETERS)
public Car buildCar(List<Part> parts) {
//...
}
@ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.RETURN_VALUE)
public Car paintCar(int color) {
//...
}
}
Although such a constraint is applicable to the parameters and return value of an executable, the target 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 the annotated element)
In these situations you don't have to specify the constraint
target. It is still recommended to do so if it increases readability
of the source code. If the constraint target is not specified in
situations where it can't be determined automatically, a
ConstraintDeclarationException
is
raised.
The postconditions of a method or constructor are declared by adding constraint annotations to the executable as shown in Example 3.4, “Declaring method and constructor return value constraints”.
Example 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<Customer> getCustomers() {
//...
}
}
The following constraints apply to the executables of
RentalStation
:
Any newly created RentalStation
object
must satisfy the @ValidRentalStation
constraint
The customer list returned by
getCustomers()
must not be
null
and must contain at least on element
Similar to the cascaded validation of JavaBeans properties (see
Section 2.1.5, “Object graphs”), the
@Valid
annotation can be used to mark executable
parameters and return values for cascaded validation. When validating a
parameter or return value annotated with @Valid
,
the constraints declared on the parameter or return value object are
validated as well.
In Example 3.5, “Marking executable parameters and return values for cascaded
validation”, the
car
parameter of the method
Garage#checkCar()
as well as the return value
of the Garage
constructor are marked for cascaded
validation.
Example 3.5. Marking executable parameters and return values 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) {
//...
}
}
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 the
checkCar()
method, the constraints on the
properties of the passed Car
object are evaluated
as well. Similarly, the @NotNull
constraint on
the name
field of Garage
is
checked when validating the return value of the
Garage
constructor.
Generally, the cascaded validation works for executables in exactly the same way as it does for JavaBeans properties.
In particular, null
values are ignored during
cascaded validation (naturally this can't happen during 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 properties marked with
@Valid
, the constraints declared on the
referenced elements will be validated as well.
Cascaded validation can not only be applied to simple object
references but also to collection-typed parameters and return values.
This means when putting the @Valid
annotation to
a parameter or return value which
is an array
implements java.lang.Iterable
or implements java.util.Map
each contained element gets validated. So when validating the
arguments of the checkCars()
method in Example 3.6, “List-typed method parameter marked for cascaded
validation”, each
element instance of the passed list will be validated and a
ConstraintViolation
created when any of the
contained Car
objects is invalid.
Example 3.6. List-typed method parameter marked for cascaded validation
package org.hibernate.validator.referenceguide.chapter03.cascaded.collection;
public class Garage {
public boolean checkCars(@Valid @NotNull List<Car> cars) {
//...
}
}
When declaring method constraints in inheritance hierarchies, it is important to be aware of the following rules:
The preconditions to be satisified 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 of behavioral
subtyping which requires that wherever a type
T
is used, also a subtype
S
of T
may be used without
altering the program's behavior.
As an example, consider a class invoking a method on an object
with the static type T
. If the runtime type of
that object was S
and S
imposed additional preconditions, the client class might fail to satisfy
these preconditions as is not aware of them. The rules of behavioral
subtyping are also known as the Liskov
substitution principle.
The Bean Validation specification implements the first rule by disallowing parameter constraints on methods 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.
Example 3.7. Illegal method parameter constraint in subtype
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 on
Car#drive()
is illegal since this method
implements the interface method
Vehicle#drive()
. Note that parameter
constraints on overriding methods are also disallowed, if the supertype
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 that class), no
parameter constraints may be specified for the method in any of the
involved types. The types in Example 3.8, “Illegal method parameter constraint in parallel types of a
hierarchy”
demonstrate a violation of that rule. The method
RacingCar#drive()
overrides
Vehicle#drive()
as well as
Car#drive()
. Therefore the constraint on
Vehicle#drive()
is illegal.
Example 3.8. Illegal method parameter constraint in parallel types of a hierarchy
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 {
public 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 value constraints 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. the constraints declared on the subtype method itself as well as any return value constraints on overridden/implemented supertype methods. This is legal as putting additional return value constraints in place may never represent a weakening of the postconditions guaranteed to the caller of a method.
So when validating the return value of the method
Car#getPassengers()
shown in Example 3.9, “Return value constraints on supertype and subtype
method”, the
@Size
constraint on the method itself as well as
the @NotNull
constraint on the implemented
interface method Vehicle#getPassengers()
apply.
Example 3.9. Return value constraints on supertype and subtype method
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() {
//...
}
}
If the validation engine detects a violation of any of the
aforementioned rules, a
ConstraintDeclarationException
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 the return value of a constructor invocation only the constraints declared on the constructor itself apply, but never any constraints declared on supertype constructors.
The validation of method constraints is done using the
ExecutableValidator
interface.
In Section 3.2.1, “Obtaining an ExecutableValidator
instance” you will
learn how to obtain an ExecutableValidator
instance
while Section 3.2.2, “ExecutableValidator methods” shows how to
use the different methods offered by this interface.
Instead of calling the ExecutableValidator methods directly from
within application code, they are usually invoked via a method
interception technology such as AOP, proxy objects, etc. This causes
executable constraints to be validated automatically and transparently
upon method or constructor invocation. Typically a
ConstraintViolationException
is raised by the
integration layer in case any of the constraints is violated.
You can retrieve an ExecutableValidator
instance via Validator#forExecutables()
as shown
in Example 3.10, “Obtaining an ExecutableValidator”.
Example 3.10. Obtaining an ExecutableValidator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
In the example the executable validator is retrieved from the default validator factory, but if required you could also bootstrap a specifically configured factory as described in Chapter 8, Bootstrapping, for instance in order to use a specific parameter name provider (see Section 8.2.4, “ParameterNameProvider”).
The ExecutableValidator
interface offers
altogether four methods:
validateParameters()
and
validateReturnValue()
for method
validation
validateConstructorParameters()
and
validateConstructorReturnValue()
for
constructor validation
Just as the methods on Validator
, all these
methods return a Set<ConstraintViolation>
which contains a ConstraintViolation
instance for
each violated constraint and which is empty if the validation succeeds.
Also all the methods have a var-args groups
parameter
by which you can pass the validation groups to be considered for
validation.
The examples in the following sections are based on the methods on
constructors of the Car
class shown in Example 3.11, “Class Car with constrained methods and
constructors”.
Example 3.11. Class Car
with constrained methods and
constructors
package 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() {
//...
}
}
The method validateParameters()
is used
to validate the arguments of a method invocation. Example 3.12, “Using
ExecutableValidator#validateParameters()” shows an
example. The validation results in a violation of the
@Max
constraint on the parameter of the
drive()
method.
Example 3.12. Using
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 that validateParameters()
validates all the parameter constraints of a method, i.e. constraints
on individual parameters as well as cross-parameter
constraints.
Using validateReturnValue()
the return
value of a method can can be validated. The validation in Example 3.13, “Using
ExecutableValidator#validateReturnValue()” yields
one constraint violation since the
getPassengers()
method is expect to return at
least one Passenger
object.
Example 3.13. Using
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 );
The arguments of constructor invocations can be validated with
validateConstructorParameters()
as shown in
method Example 3.14, “Using
ExecutableValidator#validateConstructorParameters()”.
Due to the @NotNull
constraint on the
manufacturer
parameter, the validation call returns
one constraint violation.
Example 3.14. Using
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 );
Finally, by using
validateConstructorReturnValue()
you can
valide a constructor's return value. In Example 3.15, “Using
ExecutableValidator#validateConstructorReturnValue()”,
validateConstructorReturnValue()
returns one
constraint violation, since the Car
object
returned by the constructor doesn't satisfy the
@ValidRacingCar
constraint (not shown).
Example 3.15. Using
ExecutableValidator#validateConstructorReturnValue()
//constructor for creating racing cars
Constructor<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 );
In addition to the methods introduced in Section 2.2.3, “ConstraintViolation methods”,
ConstraintViolation
provides two more methods
specific to the validation of executable parameters and return
values.
ConstraintViolation#getExecutableParameters()
returns the validated parameter array in case of method or constructor
parameter validation, while
ConstraintViolation#getExecutableReturnValue()
provides access to the validated object in case of return value
validation.
All the other ConstraintViolation
methods
generally work for method validation in the same way as for validation
of beans. Refer to the JavaDoc
to learn more about the behavior of the individual methods and their
return values during bean and method validation.
Note that getPropertyPath()
can be very
useful in order to obtain detailed information about the validated
parameter or return value, e.g. for logging purposes. In particular, you
can retrieve name and argument types of the concerned method as well as
the index of the concerned parameter from the path nodes. How this can
be done is shown in Example 3.16, “Retrieving method and parameter information”.
Example 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( "arg0", parameterNode.getName() );
assertEquals( 0, parameterNode.getParameterIndex() );
The parameter name is determined using the current
ParameterNameProvider
(see Section 8.2.4, “ParameterNameProvider”) and defaults to
arg0
, arg1
etc.
Copyright © 2009 - 2013 Red Hat, Inc. & Gunnar Morling