Cody A. Ray

Automatic model validation using Jersey, Jackson, and Hibernate Validator

January 1, 2013

So you have a RESTful Java webapp built using Jersey, some POJO/JAXB models that you’d like to serialize using Jackson, and think it’d be sweet to validate models using annotations (JSR 303 - Bean Validation)? And you like your objects to be immutable (Effective Java Item #15). You’ve come to the right place. Hibernate Validator is the reference implementation for JSR 303 and arguably the best currently available, so we’ll use it along with Jersey and Jackson to form the backbone of this recipe.

The general idea behind the ValidatingJacksonJsonProvider is that we can annotate any of the parameters used in our resource classes with @Valid to have the object automatically validated during the Jackson JSON serialization process.

So, first, annotate your POJO/JAXB object with some constraints, like @Min(0) and @NotEmpty.


package com.mydomain.myapp.model;

<p>import javax.validation.constraints.Min;</p>

<p>import com.fasterxml.jackson.annotation.JsonProperty;</p>

<p>import org.hibernate.validator.constraints.NotEmpty;</p>

<p>public class MyModel {<br />
    private final long id;<br />
    private final String name;</p>

<p>public MyModel(@JsonProperty("id") long id, @JsonProperty("name") String name) {<br />
        this.id = id;<br />
        this.name = name;<br />
    }</p>

<p>public @Min(0) getId() {<br />
        return id;<br />
    }</p>

<p>public @NotEmpty getName() {<br />
        return name;<br />
    }<br />
}

Now we can validate MyModel it in a Jersey resource class. For example, validating myModel before saving a new record to the database.

<br />
import javax.validation.Valid;<br />
import javax.ws.rs.POST;</p>

<p>import com.mydomain.myapp.model.MyModel;</p>

<p>public class MyResource {<br />
    @POST<br />
    public createMyModel(@Valid MyModel myModel) {<br />
        ...<br />
    }<br />
}

The code to implement this recipe follows. Just add this alongside your other Jersey providers (e.g., in your resources package). Its essentially a forwarding MessageBodyReader, MessageBodyWriter that delegates to the standard JacksonJsonProvider, and hooks validation into the readFrom call.

// ValidatingJacksonJsonProvider.java
package com.mydomain.myapp.resources;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.StatusType;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
 * A Jersey provider which adds validation to the basic Jackson Json provider. Any request
 * entity method parameters annotated with {@code @Valid} are validated, and an informative
 * {@code 422 Unprocessable Entity} response is returned should the entity be invalid.
 * <br/>
 * Thanks to Yammer's Dropwizard for the original idea.
 *
 * @author codyaray
 * @since 5/23/12
 */
@Provider
@Consumes({ MediaType.APPLICATION_JSON, "text/json" })
@Produces({ MediaType.APPLICATION_JSON, "text/json" })
public class ValidatingJacksonJsonProvider
    implements MessageBodyReader<Object>, MessageBodyWriter<Object> {

  // Unfortunate that this isn't defined in Response.Status
  @VisibleForTesting static final StatusType UNPROCESSABLE_ENTITY = new StatusType() {
    @Override
    public int getStatusCode() {
      return 422;
    }

    @Override
    public Response.Status.Family getFamily() {
      return Response.Status.Family.CLIENT_ERROR;
    }

    @Override
    public String getReasonPhrase() {
      return "Unprocessable Entity";
    }
  };

  private final JacksonJsonProvider delegate;
  private final Validator validator;

  @Inject
  public ValidatingJacksonJsonProvider(JacksonJsonProvider delegate, Validator validator) {
    this.delegate = delegate;
    this.validator = validator;
  }

  @Override
  public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
    throws IOException {

    Object value = parseEntity(type, genericType, annotations, mediaType, httpHeaders, entityStream);

    if (hasValidAnnotation(annotations)) {
      List<String> errors = validate(value);
      if (!errors.isEmpty()) {
        StringBuilder msg = new StringBuilder("The request entity had the following errors:\n");
        for (String error : errors) {
          msg.append("  * ").append(error).append('\n');
        }
        throw new WebApplicationException(unprocessableEntity(msg.toString()));
      }
    }

    return value;
  }

  private Object parseEntity(Class<Object> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
    throws IOException {
    return delegate.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
  }

  @Override
  public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
    throws IOException {
    delegate.writeTo(t, type, genericType, annotations, mediaType, httpHeaders, entityStream);
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType,
      Annotation[] annotations, MediaType mediaType) {
    return delegate.isWriteable(type, genericType, annotations, mediaType);
  }

  @Override
  public long getSize(Object t, Class<?> type, Type genericType,
      Annotation[] annotations, MediaType mediaType) {
    return delegate.getSize(t, type, genericType, annotations, mediaType);
  }

  @Override
  public boolean isReadable(Class<?> type, Type genericType,
      Annotation[] annotations, MediaType mediaType) {
    return delegate.isReadable(type, genericType, annotations, mediaType);
  }

  private List<String> validate(Object o) {
    Set<String> errors = Sets.newHashSet();
    Set<ConstraintViolation<Object>> violations = validator.validate(o);
    for (ConstraintViolation<Object> v : violations) {
      errors.add(String.format("%s %s (was %s)",
          v.getPropertyPath(), v.getMessage(), v.getInvalidValue()));
    }
    return ImmutableList.copyOf(Ordering.natural().sortedCopy(errors));
  }

  @VisibleForTesting static boolean hasValidAnnotation(Annotation[] annotations) {
    for (Annotation annotation : annotations) {
      if (Valid.class.equals(annotation.annotationType())) {
        return true;
      }
    }
    return false;
  }

  private static Response unprocessableEntity(String msg) {
    return Response
        .status(UNPROCESSABLE_ENTITY)
        .entity(msg)
        .type(MediaType.TEXT_PLAIN_TYPE)
        .build();
  }

}

One way to make this smarter is to throw a RuntimeException that’s registered with a Jersey ExceptionMapper. Instead of always responding with a plain-text response, this will allow Jersey to handle content-negotiation and return an error response in the appropriate format. How else could you improve this technique?

10 comments

Imported from the previous site.

Miguel Andrade· January 4, 2013
Correct me if I'm wrong, but I don't like injecting the JacksonProvider here. I would like to delegate the flow back to JAX-RS and not to Jackson in particular. This way this validation provider would be independent of the serializer. Please check this post: http://weblogs.java.net/blog/ljnelson/archive/2010/04/28/pushing-jersey-limit The author uses: @Context private Providers providers; and then: final MessageBodyWriter delegate = this.providers.getMessageBodyWriter(TheClass.class, TheClass.class, annotations, mediaType); It appears to be a more generic and standard way to obtain the correct MessageBodyWriter. Using your approach you might be even overriding @Consumes or @Produces annotations and forcing the use of Jackson, but I'm not sure. Thank you very much for your post. Looking forward to have a "plug n play" JSR-303 provider for JAX-RS. : )
John Calcote· August 23, 2017
Miguel - the post you referenced was no longer in service (java.net, etc). I was so intrigued with your ideas I went searching for it. It's now located at https://community.oracle.com/blogs/ljnelson/2010/04/28/pushing-jersey-limit.
codyaray· January 4, 2013
@Miguel Very interesting. I didn't consider delegating back to JAX-RS to make this more generic, but I believe that would work as well. It so happens that I only use JSON serialization in the project I extracted this from, so I never had the need... but I'll definitely keep this in mind in the future. Wow, that's some hairy code in the post you linked. With mixed dependency-injection styles, ThreadLocals, and reflection-based instance creation, this would be crazy hard to unit test. But cool that it seems to work for the author, at least. If I ever update this code to to support a generic serialization type, I'll look at delegating back to JAX-RS and post an update here. Heck, I might just do it for fun... I sure hope that it'll be easier than the JAXB wrapper hell he went through though. PS - This validating provider still handles content-type negotiation appropriately because its only used for JSON requests. Notice the @Consumes and @Produces annotations on the ValidatingJacksonJsonProvider.
Miguel Andrade· January 7, 2013
What I was trying to do wasn't so simple. I ended up in an infinite loop because JAX-RS always returned my custom MessageBodyWriter. It makes sense since it always uses the first it finds. We need another thing that isn't a MessageBodyWriter/Reader. These are for marshalling and unmarshalling purposes. JAX-RS 2.0 introduces Interceptors which maybe more suitable for cases like this (although JAX-RS 2.0 introduces Validation itself). I'll try to accomplish this task with some AOP, maybe.
codyaray· January 7, 2013
Cool. Let me know how it goes. Sounds like something I'll need to keep in mind if I use the delegate-to-JAXRS model. Didn't know that JAX-RS 2.0 introduced Validation. That's sweet.
Miguel Andrade· January 9, 2013
Check my question at SO for the outcome of my researches. Thank you for the inspiration. : ) http://stackoverflow.com/q/14218159/1137735
Miguel Andrade· January 7, 2013
One more thing. JSR-303 has support for groups. Example here: http://www.jroller.com/eyallupu/entry/jsr_303_beans_validation_using This is important because you probably don't want to do the same kind of validation across your entire application. An example is when you want to do a partial update (PUT). Or, for example, an id property must be null in a POST request but non-null everywhere else. Your MessageBodyWriter/Reader could check the method for the group class present in the annotation @ValidateRequest(groups = PartialUpdateGroup.class) and then apply the appropriate validation. See example of this annotation usage here: @ValidateRequest(groups = PartialUpdateGroup.class). Validation in REST WebServices appears to be quite challenging. : ) Haven't found a complete example yet. Just to let you know. Hope I've been helpful. Thank you.
Miguel Andrade· January 7, 2013
Oops, forgot the last link: http://docs.jboss.org/seam/3/rest/latest/reference/en-US/html/rest.validation.html
Billy Yuen· July 22, 2014
Why are you using Google Guice? BTW, Both delgate and validator are null in my case. So it does not work. I am using Jersey 1.x.
codyaray· August 1, 2014
Billy - I'm using Google Guice to inject my dependencies, so that I don't have to manually setup the object hierarchy. If you don't like it, you can manually initialize the object. Those values should only be null if they are not injected correctly (though normally Guice throws an error if it can't find a value to be injected).
© 2009–2026 Cody A. Ray
RSSGitHubLinkedIn