Skip to main content

JAX-RS and Bean Validation

Posted by bhaktimehta on October 30, 2013 at 10:39 PM PDT

One of the newer features introduced in JAX-RS 2.0 is the support for validating resource classes with Bean Validation
Bean Validation support comes in Jersey 2.x by adding jersey-bean-validation module to the class-path.

The following sample shows how to use Bean Validation with JAX-RS 2.0. I show how to define a custom constraint which will validate any Enum class.
Additionally I will show code on how to use bean validation with Jersey 1.x which is a reference implementation for JAX-RS 1.0

I demonstrate a sample of a Coffee shop which can take orders for coffee and get details of a particular order. The following code shows a basic CoffeesResource. Consider placing an order for coffee and the choices to be made. In post cases these choices can be validated by using java.lang.Enum. So in this sample I have a simple Enum Validator which can validate all such constraints. Here are some of the choices for a coffee order

Type:Expresso,Blended,Chocolate,Brewed
Size: Small, Medium, Large, X Large
Preference: Regular, Non Fat, Soy Milk, Whipped Cream

The following code shows how the Coffee resource looks
/**
*
* A simple JAX-RS resource which demonstrates Bean Validation
* @author Bhakti Mehta
*
*/
@Path("v1/coffees")
public class CoffeesResource {
    @Context
    javax.ws.rs.core.UriInfo uriInfo;

    @GET
    @Path("{order}")
    @Produces(MediaType.APPLICATION_XML)
    @NotNull(message="Coffee does not exist for the order id requested")
    public Coffee getCoffee(  @PathParam("order") int order) {
        return CoffeeService.getCoffee(order);

    }

    @POST
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @ValidateOnExecution
    public Response addCoffee(@Valid Coffee coffee) {
        int order = CoffeeService.addCoffee(coffee);
        return Response.created(uriInfo.getAbsolutePath())
        .entity("" + order + "").build();
    }
}

As you can see in the preceeding snippet there is a GET and POST method. The @GET annotated method getCoffee() checks if a coffee exists for the PathParam order and returns it else returns null. There is an @NotNull annotation for the method to provide a message incase there is no coffee by the order specified

The @POST annotated addCoffee() method adds a new order for the coffee. Notice the @Valid annotation for the request parameter coffee. It indicates to validate the object coffee
The next snippet shows how the Coffee object looks like.

@XmlRootElement
public class Coffee {

    @VerifyValue(Type.class)
    private String type;

    @VerifyValue(Size.class)
    private String size;

    @NotNull
    private String name;

    private double price;


    private int order;

  ....///getters and setters
}

I have two Enum Size and Type associated with the Coffee. Each of the enums are annotated with @VerifyEnum() annotation.
The @VerifyValue is a custom constraint I introduced to validate any Enum object.
It will be covered below.
The following snippet shows how the Size and Type enums.
public enum Size {
    Small("S"), Medium("M"), Large("L"), ExtraLarge("XL");
    private String value;

    private Size(String v) {
        this.value = v;
    }
    public String getValue() {
        return value;
    }
   


}

public enum Type {
    Expresso, Brewed, Blended, Chocolate ;

}

The following code shows how I have a custom bean validation constraint called VerifyValue which verifies if the value for the Enum is valid
@Retention(RUNTIME)
@Target({FIELD, METHOD})
@Documented
@Constraint(validatedBy = VerifyValueValidator.class)
public @interface VerifyValue {

        String message() default "Value specified is not valid";
        Class[] groups() default {};
        Class[] payload() default {};
        Class> value();

}

Here is the implementation for the VerifyValueValidator
/**
* Implementation for the user-defined constraint annotation @VerifyValue
* This is a general purpose validator which verifies the value for any enum
* If an Enum object has a getValue() method it will validate based on the value of the Enum
* else will use the EnumConstant
*
* @author Bhakti Mehta
*/
public class VerifyValueValidator implements ConstraintValidator {

    Class> enumClass;

    public void initialize(final VerifyValue enumObject) {
        enumClass = enumObject.value();

    }

     public boolean isValid(final Object myval,
                           final ConstraintValidatorContext constraintValidatorContext) {


        if ((myval != null) && (enumClass != null)) {
            Enum[] enumValues = enumClass.getEnumConstants();
            Object enumValue = null;

            for (Enum enumerable : enumValues)   {
                if (myval.equals(enumerable.toString()) ) return true;
                enumValue = getEnumValue(enumerable);
                if ((enumValue != null)
                        && (myval.toString().equals(enumValue.toString())))  {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Invokes the getValue() method for enum if present
     * @param enumerable The Enum object
     * @return  returns the value of enum from getValue() or
     *          enum constant
     */
    private Object getEnumValue(Enum enumerable) {
        try {
            for (Method method: enumerable.getClass().getDeclaredMethods()) {
                if (method.getName().equals("getValue")) {
                    return method.invoke(enumerable);
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

The isValid() method looks for all the Enum objects if its value matches that specified in the request and returns true else returns false. There is no straightforward method to get an enum from a value specified so I have implemented the getEnumValue() method. The EnumSet will return the EnumConstants but not an Enum based on its value.The getEnumValue() method uses reflection to invoke the getValue() method for the Enum. There can be further optimization to cache the enum based on the value in a HashMap so future references can be looked up from the map first.

Here is the Application class which has the following properties set

public Map getProperties() {
        Map properties = new HashMap() ;
        properties.put(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
       
        return properties;
    }

The ServerProperties.BV_SEND_ERROR_IN_RESPONSE is a Bean Validation (JSR-349) support customization property that will allow the validation errors to be sent back in the response
Complete sample can be downloaded from https://github.com/bhakti-mehta/samples/tree/master/jax-rs-bean-validation

Running and testing the sample

Install GlassfFish 4.0
Build the sample using mvn install
Deploy the war to GlassFish

Using curl to the URL http://localhost:8080/jax-rs-bean-validation/v1/coffees
curl
-X POST -d @test.xml http://localhost:8080/jax-rs-bean-validation/v1/coffees
--header "Content-Type:application/xml"

This will give you an order id for the coffee created.
Here is the sample input for the POST request in the test.xml file
<coffee>
<type>Expresso</type>
<size>XL</size>
  <name>Cafe Latte</name>
  <price>6.50</price>
</coffee>
Send a GET request to http://localhost:8080/jax-rs-bean-validation/v1/coffees/ 
You will get a validation error incase there is no coffee with the order specified

You can send some invalid values in the Type or the Size and see the validation errors.

Jersey 1.1.x and Bean Validation

In Jersey 1.x (RI for JAX-RS 1.1) Bean Validation is not supported by default.

With Jersey 1.x I have enabled bean validation for resource parameters by using Aspect oriented Programming by implementing a MethodInterceptor
This feature enables us to write code that is executed each time a matching method is invoked.

/**
* Intercepts resource methods before they are invoked to check for
* parameters annotated with @Valid annotation
* and validates the method arguments
*
* Incase the argument is not valid an appropriate exception with the error
* message underlying the cause of constraint violation errors is thrown
* @author Bhakti Mehta
*
*/
public class ValidationInterceptor implements MethodInterceptor {
        private final Validator validator

public static IsValidable isValidable() {
return new IsValidable();
}

       public ValidationInterceptor() {
              validator = Validation.buildDefaultValidatorFactory().getValidator();
       }


@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object argument = null;
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations != null) {
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotationsArray  = parameterAnnotations[i];
for(Annotation annotation : annotationsArray){
if (annotation instanceof Valid) {
argument = invocation.getArguments()[i];
}
}
}
}


StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\n");
if (argument != null ) {
Set> violations = validator.validate(argument);
if (!violations.isEmpty()) {
for(ConstraintViolation violation : violations) {
stringBuilder.append(violation.getMessage()).append("\n");
}
throw new ConstraintViolationException(
String.format("Error when validating method %s due to %s", method.getName(),
stringBuilder.toString())
,violations);

}
}
return invocation.proceed();

}
}
class IsValidable extends AbstractMatcher {
@Override
public boolean matches(Method method) {
Class[] interfaces = method.getDeclaringClass().getInterfaces();

if (interfaces.length > 0) {
Class resource = interfaces[0];

try {
Method resourceMethod = resource.getMethod(method.getName(),
method.getParameterTypes());
              
return Modifier.isPublic(resourceMethod.getModifiers());

} catch (NoSuchMethodException nsme) {
return false;
}
} else {
return false;
}
}
}

The above snippet shows the implementation of a MethodInterceptor

On the invoke() method we get an instance of the MethodInvocation object. As you can see in the above snippet
we get the Method from the invocation.getMethod() call and iterate through the parameter annotations for the method and check if there is an @Valid annotation
If a param has an annotation with @Valid we get the argument from the methodInvocation using invocation.getArguments()[i]

Incase the argument is not Null we call the validator.validate(argument)
For all the ConstraintViolations we throw a ConstraintViolationException with the list of violations
Finally we call through the underlying method by calling invocation.proceed()

The following code shows how to inject the ValidationInterceptor so it works with Jersey 1.1.x

public class MyConfigurator extends JerseyServletModule {
@Override
protected void configureServlets() {
                MethodInterceptor validationInterceptor = new ValidationInterceptor();
requestInjection(validationInterceptor);
bindInterceptor(Matchers.any(),
ValidationInterceptor.isValidable(), validationInterceptor);

The above code shows how we inject interceptors.

Another solution provided which can be useful is using ResourceFilterFactory as mentioned here
However in my case I needed to know the arguments of the method and validate them and I found the AOP method interceptor worked well.

Summary

This blog showed how to use Bean Validation with JAX-RS 2.0. A complete sample is available for download from https://github.com/bhakti-mehta/samples/tree/master/jax-rs-bean-validation
Additionally for people who cannot upgrade to JAX-RS 2.0 we covered how to support Bean Validation using two approaches
1. AOP with method interceptors
2. Using RequestFilterFactory and RequestFilters
Hope this was helpful.