Validating JAX-RS resources with JSR-303/349 annotations

[Updated: Aug 18, 2017, Created: Aug 11, 2017]

JAX-RS specification provides native support for validating resource classes based on Java EE Bean Validation specification (JSR-303/349).

According to JAX-RS specification, the constraint annotations can be used at the same places where we can use @MatrixParam, @QueryParam, @PathParam, @CookieParam, @HeaderParam, @Context etc. However, the constraint annotation are not allowed in resource class constructors and property setters.

Let's see a quick example to see how that works.

Example

Maven dependency

As we are going to use Jersey in this example too, we have to add following additional Jersey extension module dependency which provides support for Bean Validation (JSR-349) API.

pom.xml

<dependency>
   <groupId>org.glassfish.jersey.ext</groupId>
   <artifactId>jersey-bean-validation</artifactId>
   <version>2.25.1</version>
</dependency>

Above extension uses hibernate-validator as bean validation implementation.

A JAX-RS resource with constraint annotations

@Path("/customers")
public class CustomerResource {

  @POST
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  public String createCustomer(@NotNull @FormParam("name") String name,
                               @NotNull @FormParam("address") String address,
                               @NotNull @Pattern(regexp = "\\d{3}-\\d{3}-\\d{4}")
                               @FormParam("phone-number") String phoneNumber) {
      System.out.println("-- in createCustomer() method --");
      return String.format("created dummy customer name: %s, address: %s, phoneNumber:%s%n"
              , name, address, phoneNumber);
  }
}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run-war

JAX-RS client

public class MyClient {

  public static void main(String[] args) throws Exception {
      Form form = new Form();
      form.param("name", null)
          .param("address", null)
          .param("phone-number", "343-343-343");

      Client client = ClientBuilder.newBuilder().build();
      WebTarget target =
              client.target("http://localhost:8080/customers");
      Future<String> future = target.request(MediaType.APPLICATION_FORM_URLENCODED)
                                    .buildPost(Entity.form(form)).submit(String.class);
      System.out.println(future.get());
  }
}

Output

Caused by: java.util.concurrent.ExecutionException: javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at jersey.repackaged.com.google.common.util.concurrent.AbstractFuture$Sync.getValue(AbstractFuture.java:299)
at jersey.repackaged.com.google.common.util.concurrent.AbstractFuture$Sync.get(AbstractFuture.java:286)
at jersey.repackaged.com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:116)
at com.logicbig.example.MyClient.main(MyClient.java:24)
... 6 more
Caused by: javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1011)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:819)
at org.glassfish.jersey.client.JerseyInvocation.access$700(JerseyInvocation.java:92)
at org.glassfish.jersey.client.JerseyInvocation$5.completed(JerseyInvocation.java:776)
at org.glassfish.jersey.client.ClientRuntime.processResponse(ClientRuntime.java:196)
at org.glassfish.jersey.client.ClientRuntime.access$300(ClientRuntime.java:74)
at org.glassfish.jersey.client.ClientRuntime$2$1$1.run(ClientRuntime.java:166)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:340)
at org.glassfish.jersey.client.ClientRuntime$2$1.response(ClientRuntime.java:164)
at org.glassfish.jersey.client.internal.HttpUrlConnector$3.run(HttpUrlConnector.java:297)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at jersey.repackaged.com.google.common.util.concurrent.MoreExecutors$DirectExecutorService.execute(MoreExecutors.java:299)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at jersey.repackaged.com.google.common.util.concurrent.AbstractListeningExecutorService.submit(AbstractListeningExecutorService.java:50)
at jersey.repackaged.com.google.common.util.concurrent.AbstractListeningExecutorService.submit(AbstractListeningExecutorService.java:37)
at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:293)
at org.glassfish.jersey.client.ClientRuntime$2.run(ClientRuntime.java:180)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:340)
at org.glassfish.jersey.client.ClientRuntime$3.run(ClientRuntime.java:208)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
... 1 more

Without constraint annotations:

Let's comment out the constraint annotations:

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String createCustomer(/*@NotNull*/ @FormParam("name") String name,
                             /*@NotNull*/ @FormParam("address") String address,
                             //@NotNull @Pattern(regexp = "\\d{3}-\\d{3}-\\d{4}")
                             @FormParam("phone-number") String phoneNumber) {
    System.out.println("-- in createCustomer() method --");
    return String.format("created dummy customer name: %s, address: %s, phoneNumber:%s%n"
            , name, address, phoneNumber);
    }

Output

The output on the client:

created dummy customer name: null, address: null, phoneNumber:343-343-343

In the next example, we will see how to map a custom constraint violation error message with the response.

Example Project

Dependencies and Technologies Used :

  • jersey-server 2.25.1: Jersey core server implementation.
  • jersey-container-servlet 2.25.1: Jersey core Servlet 3.x implementation.
  • jersey-bean-validation 2.25.1: Jersey extension module providing support for Bean Validation (JSR-349) API.
  • JDK 1.8
  • Maven 3.3.9

JAX-RS Validation Example Select All Download
  • jaxrs-bean-validation
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also