Invoking JSR-303 Validation Automatically in JEE 6

Published in Java on 17-Aug-2014

JSR-303 (Bean Validation Framework) is a useful feature introduced in JEE 6. It provides the ability to define validation rules declaratively, through the use of built-in validation annotations and the ability to create custom validation behavior.

I am of a mixed mind when it comes to JSR-303. On the face of it, it seems very useful, but it has always felt like it is a second-class citizen in JEE 6 - as though it were a bolted-on afterthought rather than a full-fledged, fully integrated feature. Additionally, validation can be very complex, and while JSR-303 gives you the ability to define custom validation behavior, it often feels like it is an uphill battle to accomplish anything non-trivial.

In order to use JSR-303, you must have an object you wish to validate. All that is necessary is to take a Java Bean and add a few annotations:

package ca.quadrilateral.blog;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

public class SomeTransferObject {
    @Min(value = 1, message="The number must be between 1 and 10")
    @Max(value = 10, message="The number must be between 1 and 10")
    private Integer someNumber;

    public Integer getSomeNumber() {
        return someNumber;
    }

    public void setSomeNumber(Integer someNumber) {
        this.someNumber = someNumber;
    }
}

This class is a transfer object that contains a single property - someNumber - an Integer. We have used the built-in @Min and @Max annotations to indicate that this property should contain a value between 1 and 10.

Once we have an object to validate, we must create a Validator instance, invoke validation on the object and handle any errors that result:

final ValidatorFactory validationFactory =
    Validation.buildDefaultValidatorFactory();

final Validator validator = validationFactory.getValidator();

final Set<ConstraintViolation<?>> violations =
    validator.validate(objectToValidate);

if (!violations.isEmpty()) {
    throw new ConstraintViolationException(violations);
}

This is the primary reason that it seems to me that JSR-303 was somewhat of an afterthought in JEE 6. It provides a framework for declaratively defining the validation rules to apply but does not allow you to declaratively indicate when that validation should be invoked. Instead, several lines of boilerplate code are required each time you wish to invoke validation.

In JEE 7, changes have been made that improve this situation. If a formal method argument in a CDI (Contexts and Dependency Injection) bean, Session Bean or JAX-RS resource is annotated with @Valid, JSR-303 validation will be automatically invoked. What if you don't have the luxury of upgrading to JEE 7? If you have to make to with JEE 6, how can this situation be improved?

I have recently been handling this situation through the use of CDI interceptors. We can add CDI interceptors to CDI bean methods or Session bean methods that will automatically invoke the JSR-303 validator. This allows us to get the declarative validation invocation that seems to otherwise be absent in JEE 6.

We start by defining an interceptor binding annotation, which will allow us to attach our validation interceptor onto methods of our choosing:

package ca.quadrilateral.blog;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Validate {

}

Next we define the interceptor itself, which is responsible for actually invoking the JSR-303 validation:

package ca.quadrilateral.blog;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Interceptor
@Validate
public class ValidationInterceptor {
    @AroundInvoke
    public Object doValidation(
        final InvocationContext context)
        throws Exception {

        final Method invokedMethod = context.getMethod();

    	//Create a set to contain any validation errors
        final Set<ConstraintViolation<?>> constraintViolations =
            new HashSet<>();

        //Create a validator to do the validation
        final ValidatorFactory validationFactory =
            Validation.buildDefaultValidatorFactory();
        final Validator validator = validationFactory.getValidator();

        int argumentIndex = 0;

        //Get the list of annotations for each formal argument
        for(Annotation[] annotationArray :
            invokedMethod.getParameterAnnotations()) {

            //Look to see if the formal argument is annotated with @Valid
            for(Annotation annotation : annotationArray) {
                if (annotation.annotationType().equals(Valid.class)) {

                    //Get the argument to validate
                    final Object paramToValidate =
                        context.getParameters()[argumentIndex];

                    //Perform the validation
                    constraintViolations.addAll(
                        validator.validate(paramToValidate));
                }
            }
            argumentIndex++;
        }

        //If we have found errors, throw an exception
        if (!constraintViolations.isEmpty()) {
            throw new ConstraintViolationException(constraintViolations);
        }

        //If we have found no errors, invoke the intercepted method
        return context.proceed();
    }
}

When a service method is annotated with @Validate, this interceptor will be invoked prior to any call to that method. This interceptor performs the following actions:

The exception thrown in the case of validation errors can be handled upstream by clients of the service. If using JAX-RS for example, a custom ExceptionMapper may be created to translate the error information into a nicely formatted JSON or XML object to be consumed by the calling client.

In order to use this interceptor, all that must be done is to add the @Validate annotation to any method where we wish declarative valdation to occur, and to annotate any objects to be validated with the @Valid annotation, as can be seen here:

package ca.quadrilateral.blog;

import javax.validation.Valid;

public class SomeService implements SomeServiceInterface {
    @Override
    @Validate
    public void doSomething(@Valid final SomeTransferObject toBeValidated) {
        //Do some work after validation has executed
    }
}

All that remains afterwards is to ensure that our beans.xml file is present and lists our interceptor:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
       http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

    <interceptors>
        <class>ca.quadrilateral.blog.ValidationInterceptor</class>
    </interceptors>
</beans>

The thing that I like about this approach is that it is mostly forwards compatible - most of what is being done here is compatible with JEE 7. You get the declarative invocation of JSR-303 validation while using JEE 6, and you have no need for large amounts of boilerplate code. If at a later date you are able to upgrade to JEE 7, by simply removing the @Validate annotation and the interceptor, you will then be allowing the JEE 7 container to invoke the JSR-303 validator automatically for you.

About the Author

dan.jpg

Daniel Morton is a Software Developer with Shopify Plus in Waterloo, Ontario and the co-owner of Switch Case Technologies, a software development and consulting company. Daniel specializes in Enterprise Java Development and has worked and consulted in a variety of fields including WAN Optimization, Healthcare, Telematics, Media Publishing, and the Payment Card Industry.