Red Hat
Nov 29, 2012
by Gunnar Morling

Today it's my pleasure to announce the simultaneous release of Hibernate Validator 5.0.0.Alpha2 and 4.3.1.Final.

Hibernate Validator 5

The 5.0.0.Alpha2 release catches up with the public review draft of Bean Validation 1.1 (1.1.0.Beta2). Let's have a closer look at some of the changes in the following.

Method validation

The methods for validating method arguments and return values have been moved from javax.validation.Validator to a new dedicated interface MethodValidator. It is obtained and used like this:

MethodValidator methodValidator = Validation
    .buildDefaultValidatorFactory()
    .getValidator()
    .forMethods();

OrderService myOrderService = ...;
Method placeOrderMethod = ...;
Object[] arguments = ...;

Set<ConstraintViolation<OrderService>> = methodValidator
    .validateParameters(myOrderService, placeOrderMethod, arguments);

Also related to method validation, there is now support for cross-parameter constraints. These come in handy when several parameters of a method or constructor need to be validated together, similar to traditional class-level constraints. The following shows an example:

@ConsistentDateParameters
public void createCalendarEvent(Date start, Date end) {
    //...
}

Opposed to constraints on individual parameters, cross-parameter constraints are specified on the method itself. To make it easy for readers of your code to differentiate cross-parameter constraints from return value constraints, it's recommended to choose a name which clearly expresses the constraint's purpose.

A cross-parameter constraint is defined like this:

@Target({ METHOD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@CrossParameterConstraint(validatedBy = ConsistentDateParametersValidator.class)
@Documented
public @interface ConsistentDateParameters {
    String message() default "{ConsistentDateParameters.message}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

As you can see, this looks very similar to a normal Bean Validation constraint annotation. The only difference is that the @CrossParameterConstraint meta-annotation is used to specify the constraint's validator. The validator must support Object[] as validated type:

public class ConsistentDateParametersValidator implements ConstraintValidator<ConsistentDateParameters, Object[]> {

    @Override
    public void initialize(ConsistentDateParameters constraintAnnotation) {
        //nothing to do
    }

    @Override
    public boolean isValid(Object[] value, ConstraintValidatorContext context) {
        if ( value.length != 2 ) {
            throw new IllegalArgumentException( "Unexpected method signature" );
        }

        if ( value[0] == null || value[1] == null ) {
            return true;
        }

        if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
            throw new IllegalArgumentException( "Unexpected method signature" );
        }

        return ( ( Date ) value[0] ).before( ( Date ) value[1] );
    }
}

As this validator operates on an array of objects representing the method arguments to be validated, it is required to check the number and types of arguments first. The advantage of this approach is that it allows to implement generic cross-parameter constraints which are not specific to one method:

@AreEqual(indexes={0,1}, message="Passwords must be identical")
public void resetPassword(String password, String confirmedPassword) {
    //...
}

The Bean Validation EG also considers a more type-safe approach, which has its own quirks, though. Let us know what would work best for you.

Integration with CDI

One of the big themes in Bean Validation 1.1 is the integration with CDI. The 5.0.0.Alpha2 release brings initial support for this. So it's possible now to have dependencies injected into constraint validator implementations via CDI:

public class OrderNumberValidator implements ConstraintValidator<ValidOrderNumber, String> {

    @Inject
    private OrderValidationService orderValidationService;

    @Override
    public void initialize(ValidOrderNumber constraintAnnotation) {
        //nothing to do
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return orderValidationService.isValid(value);
    }
}

But that's not all. When invoking a method on a CDI managed bean which has parameter or return value constraints, automatically an interceptor kicks in, which validates the arguments and return value. If either is invalid, a ConstraintViolationException will be thrown. This protects the method implementation from illegal argument values and the caller from illegal return values. So you'll never have to write code like this again:

public void placeOrder(Item item, int quantity) {
    if(item == null) {
        throw new IllegalArgumentException("Item must not be null");
    }
    if(quantity < 1) {
        throw new IllegalArgumentException("Quantity must be at least 1");
    }
    
    //actual implementation
}

Instead this is sufficient:

public void placeOrder(@NotNull Item item, @Min(1) int quantity) {    
    //actual implementation
}

This does not only simplify the implementation, it also makes sure that the caller knows about the constraints, because they are part of the method's public API.

Group conversion

Finally, there is support for the conversion of the validated groups during cascaded validation, a long standing feature request with the Bean Validation spec. Remember, when annotating an element (e.g. a property or method parameter) with @Valid, validation will be propagated to the referenced object(s).

With help of the new ConvertGroup annotation it is now possible to define which group or sequence on the cascaded element shall be validated when a given group is validated on the parent. Let's look at an example:

//validation groups
public interface Complete extends Default {}
public interface BasicPostal {}
public interface FullPostal extends BasicPostal {}

public class Address {
    
    @NotNull(groups = BasicPostal.class)
    String street1;

    @NotNull
    String street2;

    @Size(groups = BasicPostal.class, min = 3)
    String zipCode = "12";

    @Size(groups = FullPostal.class, max = 2)
    String doorCode = "ABC";
}
    
public class User {

    @Valid
    @ConvertGroup.List({
        @ConvertGroup(from = Default.class, to = BasicPostal.class),
        @ConvertGroup(from = Complete.class, to = FullPostal.class)
    })
    Set<Address> addresses = new HashSet<>();
}

When validating a User object with the default group, for the associated addresses the constraints in the BasicPostal group will be validated. When validating a User object with the Complete group, this will lead to a validation of the constraints in the FullPostal group on the user's addresses.

This feature should help with cascaded validations throughout different layers of an application which all define different validation groups.

While these are the major changes of the release, there are some more notable ones, e.g. minor modifications to the metadata API, some changes around ConfigurationSource as well as several bugfixes. Not related to Bean Validation 1.1, we finished the works to clearly separate API, SPI and internal parts of the code base. The changelog has all the details for you.

Hibernate Validator 4.3

While we're heavily working on Hibernate Validator 5 in order to implement revision 1.1 of the Bean Validation spec, we decided to do a maintenance release of Hibernate Validator 4.3, the reference implementation of Bean Validation 1.0.

We fixed a couple of bugs, most notably HV-591. This caused an error when applying the @Email constraint to long strings. Interestingly, this is by far the most reported bug we've had so far in Hibernate Validator with altogether four duplicates. I'm wondering who choses an email address with over 60 characters, but we can validate it now :) Check out the change log for a complete overview of all issues fixed in 4.3.1.

Summing it up

This concludes our tour through the new Hibernate Validator releases.

As usual, both releases can be retrieved from SourceForge (4.3.1.Final, 5.0.0.Alpha2). The Maven coordinates are org.hibernate:hibernate-validator:4.3.1.Final respectively org.hibernate:hibernate-validator:5.0.0.Alpha2. Your feedback is welcome in the forum, the issue tracker and our mailing list. The new features are not described in the Hibernate Validator reference guide yet, so you might be interested in the latest specification draft.

If you have any ideas or proposals around the Bean Validation 1.1 spec, don't hesitate to engage directly with the expert group.