Hibernate.orgCommunity Documentation

Chapter 3. Declaring and validating method constraints

3.1. Declaring method constraints
3.1.1. Parameter constraints
3.1.2. Return value constraints
3.1.3. Cascaded validation
3.1.4. Method constraints in inheritance hierarchies
3.2. Validating method constraints
3.2.1. Obtaining an ExecutableValidator instance
3.2.2. ExecutableValidator methods
3.2.3. ConstraintViolation methods for method validation
3.3. Built-in method 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. That way Bean Validation constraints can be used to specify

Note

For the purpose of this reference guide, the term method constraint refers to both, method and constructor constraints, if not stated otherwise. Occasionally, the term executable is used when referring to methods and constructors.

This approach has several advantages over traditional ways of checking the correctness of parameters and return values:

Tip

In order to make annotations show up in the JavaDoc of annotated 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”.


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 as well as be in the future and finally 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.


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.


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.

Similar to the cascaded validation of JavaBeans properties (see Section 2.1.6, “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.


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 instances is invalid.


When declaring method constraints in inheritance hierarchies, it is important to be aware of the following rules:

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.


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.


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.


If the validation engine detects a violation of any of the aforementioned rules, a ConstraintDeclarationException will be raised.

Note

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.

The ExecutableValidator interface offers altogether four methods:

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”.


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”.


The parameter name is determined using the current ParameterNameProvider (see Section 8.2.4, “ParameterNameProvider) and defaults to arg0, arg1 etc.

In addition to the built-in bean and property-level constraints discussed in Section 2.3, “Built-in constraints”, Hibernate Validator currently provides one method-level constraint, @ParameterScriptAssert. This is a generic cross-parameter constraint which allows to implement validation routines using any JSR 223 compatible ("Scripting for the JavaTM Platform") scripting language, 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 from the active parameter name provider (see Section 8.2.4, “ParameterNameProvider). Example 3.17, “Using @ParameterScriptAssert shows how the validation logic of the @LuggageCountMatchesPassengerCount constraint from Example 3.2, “Declaring a cross-parameter constraint” could be expressed with the help of @ParameterScriptAssert.