Skip to main content
Last updated June 30, 2011 19:17, by spericas
Feedicon  

Validation in JAX-RS 2.0 (Draft)


Introduction


Validating data is a common task that is copied in many different layers of an application, from the presentation tier to the persistence layer. Many times the exact same validations will have to be implemented in each separate validation framework, proving time consuming and error prone. To prevent having to re-implement these validations at each layer, many developers will bundle validations directly into their classes, cluttering them with copied validation code that is, in fact, meta-data about the class itself.

JSR 303 defines a meta-data model and API for JavaBean validation. This wiki describes a proposal for using the bean validation API in a JAX-RS application. Specifically, using JSR 303 annotations in JAX-RS resource classes.

Motivating Example


JAX-RS 1.0 provides great support for extracting request values and binding them into Java fields, properties and parameters using annotations such as @HeaderParam, @QueryParam, etc. It also supports binding of request entity bodies into Java objects via non-annotated parameters (i.e., parameters that are not annotated with any of the JAX-RS annotations). Currently, any additional validation on these values in a resource class must be performed programmatically.

The purpose of this proposal is to enable validation annotations to be combined with JAX-RS annotations. For example, given the validation annotations @NotNull (built-in in JSR 303) and @Email (user defined), the following example shows how form parameters could be validated:

@Path("/")
class MyResourceClass {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public void registerUser(
        @NotNull @FormParam("firstName") String firstName,
        @NotNull @FormParam("lastName") String lastName,
        @Email @FormParam("email") String email) {
        ...
    }
}

Or alternatively using fields and properties:

@Path("/")
class MyResourceClass {

    @NotNull @FormParam("firstName")
    private String firstName;

    @NotNull @FormParam("lastName")
    private String lastName;

    private String email;

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public void registerUser() {
        ...
    }

    @Email @FormParam("email")
    public void setEmail(String email) {
        this.email = email;
    }
    ...
}

As stated above, @NotNull is a built-in annotation. The @Email annotation can be defined as follows using the validation API. First, the annotation itself must be defined by using the @Constraint meta-annotation, which also includes the name of the validator class defined below:

@Target( { METHOD, FIELD, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface Email {
    String message() default "{foo.bar.validation.constraints.email}"; 
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Please refer to JSR 303 for more information on these annotation elements. Note that the @Constraint annotation refers to the EmailValidation class which is defined as follows:

@Provider
public class EmailValidator implements ConstraintValidator<Email, String> {
    public void initialize(Email email) { 
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        // Ensure value is a valid e-mail address
        return true;
    }
}

As seen in the example above, validators are also JAX-RS providers and can registered and discovered as such.

Validating Resource Class Instances


So far we've seen examples that show how to validate method parameters, fields and properties. Constraint annotations are also allowed on resource classes. For example,

@Path("/")
@NotNullNames
class MyResourceClass {

    @FormParam("firstName")
    private String firstName;

    @FormParam("lastName")
    private String lastName;

    @Email @FormParm("email")
    private String email;

    ...
}

where @NotNullNames is defined as follows:

@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = NotNullNamesValidator.class)
public @interface NotNullNames {
    String message() default "{foo.bar.validation.constraints.notnull}"; 
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

@Provider
public class NotNullNamesValidator implements 
        ConstraintValidator<NotNullNames, MyResourceClass> {
    public void initialize(NotNullNames notnull) { 
    }

    public boolean isValid(MyResourceClass value, ConstraintValidatorContext context) {
        return value.getFirstName() != null && value.getLastName() != null;
    }
}

Entity Validation


Request entity bodies mapped to non-annotated parameters can also be validated using JSR 303. For example, a @CheckUser annotation and validator could be defined to check the validity of a User instance (e.g., non-null last name, valid e-mail address, etc.). Given that annotation, it could be used on the entity parameter as follows:

@Path("/")
class MyResourceClass {

    @POST
    @Consumes("application/xml")
    public void registerUser(@CheckUser User c) {
        ...
    }
}

Similarly, returned instances can be validated by annotating the resource method itself. For example, using @CheckUser:

@Path("/")
class MyResourceClass {

    @GET
    @Path("{id}")
    @Produces("application/xml")
    @CheckUser
    public User getUser(@PathParam("id") String id) {
        User u = findUser(id);
        return u;
    }
    ...
}

JAX-RS implementations MUST report an error if the type of the validation does not match the return type of the resource method. In the example above, the validator for the @CheckUser annotation must be defined for the type User.

Alternatively, a bean that is used as an entity parameter or as the return value in a resource method may already be decorated with constraint annotations. The BV annotation @Valid can be used to enable validation on such beans. The example above could also be implemented as follows:

@CheckUser
class User { ... }

@Path("/")
class MyResourceClass {

    @POST
    @Consumes("application/xml")
    public void registerUser(@Valid User c) {
        ...
    }
}

In this case, the User bean is annotated by @CheckUser and the @Valid annotation on the method parameter is used to turn on validation on the request entity. The @Valid annotation can also be used to turn on validation on the return value of a resource method.

Validation Points


Validation (or constraint) annotations are supported in resource classes. They are allowed in the same locations as the following annotations: @MatrixParam, @QueryParam, @PathParam, @CookieParam, @HeaderParam and @Context. Namely, in public constructor parameters, method parameters, fields and bean properties. In sub-resource classes, whose instances are returned by sub-resource locators, constraint annotations follow the same restrictions as other annotations (regarding constructors, fields and properties). In addition, they can also decorate resource classes, entity parameters and resource methods as shown above.

The default resource class instance lifecycle is per-request in JAX-RS. Implementations MAY support other lifecycles; the same caveats related to the use of other annotations in resource class apply to constraint annotations. For example, a constraint validation annotating a constructor parameter in a resource class whose lifecycle is singleton (per application) will only be executed once.

Constraint validation MUST take place after data binding. First, the data is extracted from the request; second it is converted according to the type of the parameter, field or property and then validated according to the constraint annotations. All constraint annotations used in resource classes are validated in the Default group (see JSR 303 for more information on validation groups). The order in which validations are checked is as follows: constraint validations on fields and properties are checked first, on resource classes are checked next and on method parameters are checked last. Constraint annotations within each group are checked in an unspecified order.

Annotation Inheritance


The rule for inheritance of constraint annotations is the same as that for all the other JAX-RS annotations (see Section 3.6). Namely, constraint annotations on methods and method parameters are inherited from interfaces and super-classes, with the latter taking precedence over the former when sharing common methods. For example:

interface MyInterface {
    @GET
    @Path("{id}")
    @Produces("application/xml")
    @CheckUser
    public User getUser(@Pattern("[0-9]+") @PathParam("id") String id);
}

@Path("/")
class MyResourceClass implements MyInterface {

    public User getUser(String id) {
        User u = findUser(id);
        return u;
    }
    ...
}

In the example above, the constraint annotations @CheckUser and @Pattern will be inherited by the getUser() method in MyResourceClass. If the getUser() method in MyResourceClass is decorated with any annotation, constraint or otherwise, all of the annotations in the interface will be ignored.

Naturally, since fields in super-classes that are visible in subclasses cannot be overridden, all their annotations (including their constraint annotations) are inherited.

Validation Errors


The Bean Validation API reports error conditions using exceptions of type javax.validation.ValidationException or any of its subclasses, including javax.validation.ConstraintValidationException. A JAX-RS runtime MUST define built-in exception mapping providers for javax.validation.ValidationException and javax.validation.ConstraintValidationException to handle these exceptions. The provider for the former must map the exception to an instance of Response whose status code is set to 500 (Internal Server Error) and (optionally) an entity that includes a description of the problem; the provider for the latter must map the exception to an instance of Response whose status code is set to 400 (Bad Request) and (optionally) an entity that includes a description of the constraint validations.

A constraint violation error reported for the return type of a resource method should not return a 400 (Bad Request) HTTP code. Instead, this condition should be reported as a 500 (Internal Server Error) response. However, it isn't clear how to write an exception mapper that can discern request vs. response violations.

Applications can supply custom exception mapping providers for any exception, including those of type javax.validation.ValidationException or any of its subclasses. A JAX-RS implementation MUST always use the provider whose generic type is the nearest superclass of the exception, with application-defined providers taking precedence over built-in providers.

Validation in Client API (Brainstorming)


Binding via Annotation Classes

Constraint annotations and validators can also be used to validate entities when using the client API. A validator can be registered like any other provider; annotation classes can be used to specify the type of validation requested. The following example shows how to validate a String using the EmailValidator defined above:

  Client c = ...
  c.register(EmailValidator.class);
  String response = c.resourceUri("...").get().invoke(String.class, Email.class);

Given that annotations cannot be instantiated programmatically, only annotation classes such as Email.class can be used in the client API. As a result, JAX-RS implementations will set the annotation parameter to null in the call to ConstraintValidator.initialize(). Validators that are shared between the client and the server must guard for this condition. Moreover, annotations that have required elements (like the built-in @Pattern annotation) will not be available for use in the client API.

Is it worth pursuing this approach in which validators are reusable between the client and the server? If so, using annotation classes seems necessary. Note that validators are selected based on the both the type of the instance being validated and the annotation. In the example above, String.class and Email.class are used to find the appropriate validator.

The following example shows how to validate the request entity using @CheckUser and the response entity using @Email:

  void putUser(User user) {
    Client c = ...
    c.register(EmailValidator.class).register(CheckUserValidator.class);
    String response = c.resourceUri("...").type(...).put()
        .entity(user, CheckUser.class).invoke(String.class, Email.class);
  }

Note the extra parameter passed in the call to the methods entity() and invoke().

How can we support default annotations like @NotNull or @Pattern? Could we pre-register validators for all these built-in annotations?

Binding via Annotation Sub-Class Instances

One way to get around the problem of being unable to instantiate annotations is to define sub-classes for them (they are interfaces after all). For each of the built-in annotations in the BV API, a sub-class could be defined as shown in the following example:

public class Pattern implements javax.validation.constraints.Pattern {

    private String regexp;
    
    public Pattern(String regexp) {
        this.regexp = regexp;
    }
    
    @Override
    public String regexp() {
        return regexp;
    }
    ...
}

Given that it uses the same name, this sub-class will need to reside in a different package. Suitable constructors could be added to these sub-classes for client applications to use. For example, a zip code could be validated using the Pattern class above as follows:

    String zipcode = c.resourceUri("...").get().invoke(String.class, new Pattern("[0-9]+"));

Validators could be properly initialized given that there is an instance of an annotation to pass as a parameter. The disadvantage of this approach is the number of classes that would need to be defined to accomodate this use case. In particular, users would need to define classes that extend their annotations when using them on the client side.

Binding using Annotation Proxies

The JDK produces annotation instances using dynamic proxies that implement annotation interfaces. Unfortunately, this functionality isn't publicly available in Java SE (see sun.reflect.annotation.AnnotationInvocationHandler). Other libraries (e.g., AnnotationLiteral in CDI) have attempted to circumvent this issue by providing their own implementation of annotation proxies. Instantiation support of annotations seems beyond the scope of JAX-RS, but if that support is available either in JAX-RS implementations or in JAX-RS dependencies like BV, then it could be used as part of the Client API.

Using an AnnotationBuilder class, the following code could be written:

Pattern pp = AnnotationBuilder.element("value", "[0-9]+").build(Pattern.class);
String zipcode = c.resourceUri("http://example.com/user/25/zipcode").get().invoke(String.class, pp);

It's not difficult to provide an implementation of AnnotationBuilder. See sun.reflect.annotation.AnnotationInvocationHandler for an example (what it's more difficult is to provide an implementation that is compatible with the one in JDK, which is internal and therefore subject to change between versions).

Direct Binding using Validators

Alternatively, we could bind validators directly to instances without using annotation classes. In the example above, we would use the validator classes directly in the calls to entity() and invoke() as shown next:

  void putUser(User user) {
    Client c = ...
    String response = c.resourceUri("...").type(...).put()
        .entity(user, CheckUserValidator.class).invoke(String.class, EmailValidator.class);
  }

The types for the invoke() and entity() methods would be defined as follows: for T <: HttpRequest,

  <Q, S extends java.lang.annotation.Annotation> Q invoke(
            Class<Q> responseType, 
            Class<? extends ConstraintValidator<S, Q>> validatorClass) 
            throws InvocationException;

  <R, S extends java.lang.annotation.Annotation> T entity(
            R entity,
            Class<? extends ConstraintValidator<S, R>> validatorClass) 
            throws InvocationException;

Note how the type parameters Q and R relate the method parameters. The Java type system would therefore prevent passing a validator class that is invalid for the corresponding request or response entity. Just like in the first approach, since there are no annotations, JAX-RS implementations will set the annotation parameter to null in the call to ConstraintValidator.initialize().

It is also possible to register a validator directly in a Client instance. Such a validator will be used to validate every request and response entity associated with that Client instance, which may result in unexpected errors. For example,

    Client c = ...
    c.register(EmailValidator.class);      // Register validator for String instances   
    // EmailValidator checks this response
    String email = c.resourceUri("http://example.com/user/25/email").get().invoke(String.class);
    // Oops! Validator also checks this response
    String zipcode = c.resourceUri("http://example.com/user/25/zipcode").get().invoke(String.class);

Thus, it is preferable to reduce the scope of validators by specifying them as parameters to invoke() and entity(). Any validator specified in this manner would take precedence over a validator registered in a Client instance. JAX-RS implementations MUST throw a ClientException for any attempt to register a second validator for the same type in a Client instance. Just like on the server side, all validators are executed in the Default group.

 
 
Close
loading
Please Confirm
Close