Chapter 4. RESTful Web Services

RESTful Web Services are defined as JSR 339, and the complete specification can be downloaded.

REST is an architectural style of services that utilizes web standards. Web services designed using REST are called RESTful web services, and their main principles are:

  • Everything can be identified as a resource, and each resource can be uniquely identified by a URI.

  • A resource can be represented in multiple formats, defined by a media type. The media type will provide enough information on how the requested format needs to be generated. Standard methods are defined for the client and server to negotiate on the content type of the resource.

  • Use standard HTTP methods to interact with the resource: GET to retrieve a resource, POST to create a resource, PUT to update a resource, and DELETE to remove a resource.

  • Communication between the client and the endpoint is stateless. All the associated state required by the server is passed by the client in each invocation.

Java API for RESTful web services (JAX-RS) defines a standard annotation-driven API that helps developers build a RESTful web service in Java and invoke it. The standard principles of REST, such as identifying a resource as a URI, a well-defined set of methods to access the resource, and multiple representation formats of a resource, can be easily marked in a POJO via annotations.

Resources

A simple RESTful web service can be defined as a resource using @Path:

@Path("orders")
public class OrderResource {
  @GET
  public List<Order> getAll() { 
    //. .  .
  }

  @GET
  @Path("{oid}")
  public Order getOrder(@PathParam("oid")int id) {
    //. . . 
  }
}

@XmlRootElement
public class Order {
  int id;
  //. . .
}

In this code:

  • OrderResource is a POJO class and is published as a RESTful resource at the orders path when we add the class-level @Path annotation.

  • The Order class is marked with the @XmlRootElement annotation, allowing a conversion between Java and XML.

  • The getAll resource method, which provides a list of all orders, is invoked when we access this resource using the HTTP GET method; we identify it by specifying the @GET annotation on the method.

  • The @Path annotation on the getOrder resource method marks it as a subresource that is accessible at orders/{oid}.

  • The curly braces around oid identify it as a template parameter and bind its value at runtime to the id parameter of the getOrder resource method.

  • The @PathParam can also be used to bind template parameters to a resource class field.

Typically, a RESTful resource is bundled in a .war file along with other classes and resources. The Application class and @ApplicationPath annotation are used to specify the base path for all the RESTful resources in the packaged archive. The Application class also provides additional metadata about the application.

Let’s say this POJO is packaged in the store.war file, deployed at localhost:8080, and the Application class is defined:

@ApplicationPath("webresources")
public class ApplicationConfig extends Application {
}

You can access a list of all the orders by issuing a GET request to:

http://localhost:8080/store/webresources/orders

You can obtain a specific order by issuing a GET request to:

http://localhost:8080/store/webresources/orders/1

Here, the value 1 will be passed to getOrder’s method parameter id. The resource method will locate the order with the correct order number and return back the Order class. The @XmlRootElement annotation ensures that an automatic mapping from Java to XML occurs following JAXB mapping and an XML representation of the resource is returned.

A URI may pass HTTP query parameters using name/value pairs. You can map these to resource method parameters or fields using the @QueryParam annotation. If the resource method getAll is updated such that the returned results start from a specific order number, the number of orders returned can also be specified:

public List<Order> getAll(@QueryParam("start")int from, 
                          @QueryParam("page")int page) {
  //. . .
}

And the resource is accessed as:

http://localhost:8080/store/webresources/orders?start=10&page=20

Then 10 is mapped to the from parameter and 20 is mapped to the page parameter.

By default, a resource method is required to wait and produce a response before returning to the JAX-RS implementation, which then returns control to the client. JAX-RS 2 allows for an asynchronous endpoint that informs the JAX-RS implementation that a response is not readily available upon return but will be produced at a future time. It achieves this by first suspending the client connection and later resuming when the response is available:

@Path("orders")
public class OrderResource {
  @GET
  public void getAll(@Suspended final AsyncResponse ar) { 
    executor.submit(new Runnable() {

      @Override
      public void run() {
        List<Order> response = new ArrayList<>();
        //. . .
        ar.resume(response);
      }
    });
  }
}

In this code:

  • The getAll method is marked to produce an asynchronous response. We identify this by injecting a method parameter of the class AsyncResponse using the new annotation @Suspended. The return type of this method is void.

  • This method returns immediately after forking a new thread, likely using ManagedExecutorService as defined by Concurrency Utilities for Java EE. The client connection is suspended at this time.

  • The new thread executes the long-running operation and resumes the connection by calling resume when the response is ready.

You can receive request processing completion events by registering an implementation of CompletionCallback:

public class OrderResource {
  public void getAll(@Suspended final AsyncResponse ar) {
    ar.register(new MyCompletionCallback());
  }

  class MyCompletionCallback implements CompletionCallback {

    @Override
    public void onComplete(Throwable t) {
      //. . .
    }
  }
}

In this code, the onComplete method is invoked when the request processing is finished, after a response is processed and is sent back to the client, or when an unmapped throwable has been propagated to the hosting I/O container.

You can receive connection-related life-cycle events by registering an implementation of ConnectionCallback:

public class OrderResource {
  public void getAll(@Suspended final AsyncResponse ar) {
    ar.register(new MyCompletionCallback());
  }

  class MyCompletionCallback implements CompletionCallback {

    @Override
    public void onDisconnect(AsyncResponse ar) {
      //. . .
    }
  }
}

In this code, the onDisconnect method is invoked in case the container detects that the remote client connection associated with the asynchronous response has been disconnected.

Binding HTTP Methods

JAX-RS provides support for binding standard HTTP GET, POST, PUT, DELETE, HEAD, and OPTIONS methods using the corresponding annotations described in Table 4-1.

Table 4-1. HTTP methods supported by JAX-RS

HTTP methodJAX-RS annotation
GET@GET
POST@POST
PUT@PUT
DELETE@DELETE
HEAD@HEAD
OPTIONS@OPTIONS

Let’s take a look at how @POST is used. Consider the following HTML form, which takes the order identifier and customer name and creates an order by posting the form to webresources/orders/create:

<form method="post" action="webresources/orders/create">
  Order Number: <input type="text" name="id"/><br/>
  Customer Name: <input type="text" name="name"/><br/>
  <input type="submit" value="Create Order"/>
</form>

The updated resource definition uses the following annotations:

@POST
@Path("create")
@Consumes("application/x-www-form-urlencoded")
public Order createOrder(@FormParam("id")int id, 
                         @FormParam("name")String name) {
  Order order = new Order();
  order.setId(id);
  order.setName(name);
  return order;
}

The @FormParam annotation binds the value of an HTML form parameter to a resource method parameter or a field. The name attribute in the HTML form and the value of the @FormParam annotation are exactly the same to ensure the binding. Clicking the submit button in this form will return the XML representation of the created Order. A Response object may be used to create a custom response.

The following code shows how @PUT is used:

@PUT
@Path("{id}")
@Consumes("*/xml")
public Order putXml(@PathParam("id")int id, 
                    String content) {
  Order order = findOrder(id);
  // update order from "content"
  . . .
  return order;
}

The resource method is marked as a subresource, and {id} is bound to the resource method parameter id. The contents of the body can be any XML media type as defined by @Consumes and are bound to the content method parameter. A PUT request to this resource may be issued as:

curl -i -X PUT -d "New Order" 
    http://localhost:8080/store/webresources/orders/1

The content method parameter will have the value New Order.

Similarly, an @DELETE resource method can be defined as follows:

@DELETE
@Path("{id}")
public void putXml(@PathParam("id")int id) {
  Order order = findOrder(id);
  // delete order
}

The resource method is marked as a subresource, and {id} is bound to the resource method parameter id. A DELETE request to this resource may be issued as:

curl -i -X DELETE 
    http://localhost:8080/store/webresources/orders/1

The content method parameter will have the value New Order.

The HEAD and OPTIONS methods receive automated support from JAX-RS.

The HTTP HEAD method is identical to GET except that no response body is returned. This method is typically used to obtain metainformation about the resource without requesting the body. The set of HTTP headers in response to a HEAD request is identical to the information sent in response to a GET request. If no method is marked with @HEAD, an equivalent @GET method is called, and the response body is discarded. The @HEAD annotation is used to mark a method serving HEAD requests:

@HEAD
@Path("{id}")
public void headOrder(@PathParam("id")int id) {
  System.out.println("HEAD");
}

This method is often used for testing hypertext links for validity, accessibility, and recent modification. A HEAD request to this resource may be issued as:

curl -i -X HEAD 
    http://localhost:8080/store/webresources/orders/1

The HTTP response header contains HTTP/1.1 204 No Content and no content body.

The HTTP OPTIONS method requests the communication options available on the request/response identified by the URI. If no method is designated with @OPTIONS, the JAX-RS runtime generates an automatic response using the annotations on the matching resource class and methods. The default response typically works in most cases. @OPTIONS may be used to customize the response to the OPTIONS request:

@OPTIONS
@Path("{id}")
  public Response options() {
  // create a custom Response and return
}

An OPTIONS request to this resource may be issued as:

curl -i -X OPTIONS 
    http://localhost:8080/store/webresources/orders/1

The HTTP Allow response header provides information about the HTTP operations permitted. The Content-Type header is used to specify the media type of the body, if any is included.

In addition to the standard set of methods supported with corresponding annotations, HttpMethod may be used to build extensions such as WebDAV.

Multiple Resource Representations

By default, a RESTful resource is published or consumed with the */* MIME type. A RESTful resource can restrict the media types supported by request and response using the @Consumes and @Produces annotations, respectively. These annotations may be specified on the resource class or a resource method. The annotation specified on the method overrides any on the resource class.

Here is an example showing how Order can be published using multiple MIME types:

@GET
@Path("{oid}")
@Produces({"application/xml", "application/json"})
public Order getOrder(@PathParam("oid")int id) { . . . }

The resource method can generate an XML or JSON representation of Order. The exact return type of the response is determined by the HTTP Accept header in the request.

Wildcard pattern matching is supported as well. The following resource method will be dispatched if the HTTP Accept header specifies any application MIME type such as application/xml, application/json, or any other media type:

@GET
@Path("{oid}")
@Produces("application/*")
public Order getOrder(@PathParam("oid")int id) { . . . }

Here is an example of how multiple MIME types may be consumed by a resource method:

@POST
@Path("{oid}")
@Consumes({"application/xml", "application/json"})
public Order getOrder(@PathParam("oid")int id) { . . . }

The resource method invoked is determined by the HTTP Content-Type header of the request.

JAX-RS 2.0 allows you to indicate a media preference on the server side using the qs (short for “quality on server”) parameter.

qs is a floating-point number with a value in the range of 0.000 through 1.000 and indicates the relative quality of a representation compared to the others available, independent of the client’s capabilities. A representation with a qs value of 0.000 will never be chosen. A representation with no qs parameter value is given a qs factor of 1.0:

@POST
@Path("{oid}")
@Consumes({"application/xml; qs=0.75", "application/json; qs=1"})
public Order getOrder(@PathParam("oid")int id) { . . . }

If a client issues a request with no preference for a particular representation or with an Accept header of application/*;, then the server will select a representation with a higher qs value—application/json in this case.

qs values are relative and, as such, are only comparable to other qs values within the same @Produces annotation instance.

You can define a mapping between a custom representation and a corresponding Java type by implementing the MessageBodyReader and MessageBodyWriter interfaces and annotating with @Provider.

Binding a Request to a Resource

By default, a new resource is created for each request to access a resource. The resource method parameters, fields, or bean properties are bound by way of xxxParam annotations added during object creation time. In addition to @PathParam and @QueryParam, the following annotations can be used to bind different parts of the request to a resource method parameter, field, or bean property:

  • @CookieParam binds the value of a cookie:

    public Order getOrder(
        @CookieParam("JSESSIONID")String sessionid) {
            //. . .
    }

    This code binds the value of the "JSESSIONID" cookie to the resource method parameter sessionid.

  • @HeaderParam binds the value of an HTTP header:

    public Order getOrder(
        @HeaderParam("Accept")String accept) {
      //. . .
    }
  • @FormParam binds the value of a form parameter contained within a request entity body. Its usage is displayed in Binding HTTP Methods.

  • @MatrixParam binds the name/value parameters in the URI path:

    public List<Order> getAll(
        @MatrixParam("start")int from, 
        @MatrixParam("page")int page) {
      //. . .
    }

    And the resource is accessed as:

    http://localhost:8080/store/webresources/orders;
    start=10;
    page=20

    Then 10 is mapped to the from parameter and 20 is mapped to the page parameter.

You can obtain more details about the application deployment context and the context of individual requests using the @Context annotation.

Here is an updated resource definition where more details about the request context are displayed before the method is invoked:

@Path("orders")
public class OrderResource {

  @Context Application app;
  @Context UriInfo uri; 
  @Context HttpHeaders headers;
  @Context Request request;

  @Context SecurityContext security;
  @Context Providers providers;

  @GET
  @Produces("application/xml")
  public List<Order> getAll(@QueryParam("start")int from, 
                            @QueryParam("end")int to) {
    //. . .(app.getClasses());
    //. . .(uri.getPath());
    //. . .(headers.getRequestHeader(HttpHeaders.ACCEPT));
    //. . .(headers.getCookies());
    //. . .(request.getMethod());
    //. . .(security.isSecure());
    //. . .
  }
}

In this code:

  • UriInfo provides access to application and request URI information.

  • Application provides access to application configuration information.

  • HttpHeaders provides access to HTTP header information either as a Map or as convenience methods. Note that @HeaderParam can also be used to bind an HTTP header to a resource method parameter, field, or bean property.

  • Request provides a helper to request processing and is typically used with Response to dynamically build the response.

  • SecurityContext provides access to security-related information about the current request.

  • Providers supplies information about runtime lookup of provider instances based on a set of search criteria.

Entity Providers

JAX-RS defines entity providers that supply mapping services between on-the-wire representations and their associated Java types. The entities, also known as “message payload” or “payload,” represent the main part of an HTTP message. These are specified as method parameters and return types of resource methods. Several standard Java types—such as String, byte[], javax.xml.bind.JAXBElement, java.io.InputStream, java.io.File, and others—have a predefined mapping and are required by the specification. Applications may provide their own mapping to custom types using the MessageBodyReader and MessageBodyWriter interfaces. This allows us to extend the JAX-RS runtime easily to support our own custom entity providers.

The MessageBodyReader interface defines the contract for a provider that supports the conversion of a stream to a Java type. Conversely, the MessageBodyWriter interface defines the contract for a provider that supports the conversion of a Java type to a stream.

If we do not specify @XmlRootElement on OrderResource, then we need to define the mapping between XML to Java and vice versa. Java API for XML Processing can be used to define the mapping between the Java type to XML and vice versa. Similarly, Java API for JSON Processing can be used to define the two-way mapping between Java and JSON:

@Provider
@Produces("application/json")
public class OrderReader implements MessageBodyReader<Order> {
  //. . .

  @Override
  public void writeTo(Order o,
                      Class<?> type, 
                      Type t, 
                      Annotation[] as, 
                      MediaType mt, 
                      MultivaluedMap<String, Object> mm, 
                      OutputStream out)
          throws IOException, WebApplicationException {
    JsonGeneratorFactory factory = Json.createGeneratorFactory();
    JsonGenerator gen = factory.createGenerator(out);
    gen.writeStartObject()
       .write("id", o.getId())
       .writeEnd();
  }
}

This code shows the implementation of the MessageBodyWriter.writeTo method, which is responsible for writing the object to an HTTP response. The method is using the Streaming API defined by Java API for JSON Processing to write Order o to the underlying OutputStream out for the HTTP entity. The implementation class needs to be marked with @Provider to make it discoverable by the JAX-RS runtime on the server side. The providers need to be explicitly registered on the client side. @Produces ensures that this entity provider will only support the specified media type:

@Provider
@Consumes("application/json")
public class OrderReader implements MessageBodyReader<Order> {
  //. . .

  @Override
  public Order readFrom(Class<Order> type, 
                      Type t, 
                      Annotation[] as, 
                      MediaType mt, 
                      MultivaluedMap<String, String> mm, 
                      InputStream in) 
          throws IOException, WebApplicationException {
    Order o = new Order();
    JsonParser parser = Json.createParser(in);
    while (parser.hasNext()) {
      switch (parser.next()) {
        case KEY_NAME:
          String key = parser.getString();
          parser.next();
          switch (key) {
            case "id":
              o.setId(parser.getIntValue());
              break;
            default:
              break;
          }
          break;
        default:
          break;
      }
    }

    return o;
  }
}

This code shows the implementation of the MessageBodyReader.readFrom method, which is responsible for reading the object from the HTTP request. This method is using the Streaming API defined by Java API for JSON Processing to read Order o from the InputStream in for the HTTP entity. The implementation class needs to be marked with @Provider to make it discoverable by the JAX-RS runtime. The providers need to be explicitly registered on the client side. @Consumes ensures that this entity provider will only support the specified media type.

Client API

JAX-RS 2 adds a new Client API that can be used to access web resources and provides integration with JAX-RS providers. Without this API, users must use a low-level HttpUrlConnection to access the REST endpoint:

Client client = ClientBuilder.newClient();
Order order = client
  .target("http://localhost:8080/store/webresources/orders")
  .path("{oid}")
  .resolveTemplate("oid", 1)
  .request()
  .get(Order.class);

This code uses the fluent builder pattern and works as follows:

  • ClientBuilder is the entry point to the client API. It is used to obtain an instance of Client that uses method chaining to build and execute client requests in order to consume the responses returned.

    Clients are heavyweight objects that manage the client-side communication infrastructure. Initialization as well as disposal of a Client instance may be a rather expensive operation. It is therefore recommended that you construct only a small number of Client instances in the application.

  • We create WebTarget by specifying the URI of the web resource. We then use these targets to prepare client request invocation by resolving the URI template, using the resolveTemplate method for different names. We can specify additional query or matrix parameters here using the queryParam and matrixParam methods, respectively.

  • We build the client request by invoking the request method.

  • We invoke the HTTP GET method by calling the get method. The Java type of the response entity is specified as the parameter to the invoked method.

The fluency of the API hides its complexity, but a better understanding of the flow allows us to write better code.

We can make HTTP POST or PUT requests by using the post or put methods, respectively:

Order order = 
  client
    .target(...)
    .request()
    .post(Entity.entity(new Order(1), "application/json"), 
      Order.class);

In this code, a new message entity is created with the specified media type, a POST request is created, and a response of type Order is expected. There are other variations of the Entity.entity method that would allow us to manipulate the request. The Entity class defines variants of the most popular media types. It also allows us to POST an HTML form using the form method.

We can make an HTTP DELETE request by identifying the resource with the URI and using the delete method:

client
  .target("...")
  .target("{oid}")
  .resolveTemplate("oid", 1)
  .request()
  .delete();

We need to explicity register entity providers using the register method:

Order o = client
           .register(OrderReader.class)
           .register(OrderWriter.class)
           .target(...)
           .request()
           .get(Order.class);

By default, the client is invoked synchronously but can be invoked asynchronously if we call the async method as part of the fluent builder API and (optionally) register an instance of InvocationCallback:

Future<Order> f = client
  .target("http://localhost:8080/store/webresources/orders")
  .path("{oid}")
  .resolveTemplate("oid", 1)
  .request()
  .async()
  .get();
//. . .
Order o = f.get();

In this code, the call to get after async is called returns immediately without blocking the caller’s thread. The response is a Future instance that can be used to monitor or cancel asynchronous invocation or retrieve results.

Optionally, InvocationCallback can be registered to receive the events from the asynchronous invocation processing:

client
  .target("http://localhost:8080/store/webresources/orders/{oid}")
  .resolveTemplate("oid", 1)
  .request()
  .async()
  .get(new InvocationCallback<Order>() {

    @Override
    public void completed(Order o) {
      //. . .
    }

    @Override
    public void failed(Throwable t) {
      //. . .
    }
  });

The completed method is called on successful completion of invocation, and the response data is made available in the parameter o. The failed method is called when the invocation is failed for any reason, and the parameter t contains failure details.

A more generic client request can be prepared and executed at a later time. This enables a separation of concerns between the creator and the submitter:

Invocation i1 = client.target(...).request().buildGet();
Invocation i2 = client
                 .target(...)
                 .request()
                 .post(Entity.entity(new Order(1),
                    "application/json"));
//. . .
Response r = i1.invoke();
Order o = i2.invoke(Order.class);

In this code, a GET request is prepared and stored as i1, and a POST request is prepared and stored as i2. These requests are then executed at a later time via the invoke method and the result retrieved. In the first case, a more generic Response is returned, which can then be used to extract the result and other metadata about it. In the second case, an Order instance is returned because of the type specified before.

You can submit these as asynchronous requests using the submit method:

Future<Response> f1 = i1.submit();
Future<Order> f2 = i2.submit(Order.class);
//. . .
Response r1 = f1.get();
Order r11 = r1.readEntity(Order.class);
Order r2 = f2.get();

In this code, we submit the Invocations for asynchronous execution using the submit method. A Future response object is returned in both cases. After waiting for some time, we can obtain the first result by calling the get method on the returned Future. In the first case, we need to extract the exact result by calling the readEntity method. In the second case, an Order response is returned directly because of the type specified during the submit invocation.

Mapping Exceptions

An application-specific exception may be thrown from within the resource method and propagated to the client. The application can supply checked or exception mapping to an instance of the Response class. Let’s say the application throws the following exception if an order is not found:

public class OrderNotFoundException 
    extends RuntimeException {

  public OrderNotFoundException(int id) {
    super(id + " order not found");
  }
    
}

The method getOrder may look like:

@Path("{id}")
public Order getOrder(@PathParam("id")int id) {
  Order order = null;
  if (order == null) {
    throw new OrderNotFoundException(id);
  }
  //. . .
  return order;  
}

The exception mapper will look like:

@Provider
public class OrderNotFoundExceptionMapper 
    implements ExceptionMapper<OrderNotFoundException> {

  @Override
  public Response toResponse(
OrderNotFoundException exception) {
    return Response
       .status(Response.Status.PRECONDITION_FAILED)
       .entity("Response not found")
       .build();
  }
    
}

This ensures that the client receives a formatted response instead of just the exception being propagated from the resource.

Filters and Entity Interceptors

JAX-RS 2 defines extension points to customize the request/response processing on both the client and server side. These are used to extend an implementation in order to provide capabilities such as logging, confidentiality, and authentication. The two kinds of extension points are: filters and entity interceptors.

Filters are mainly used to modify or process incoming and outgoing request or response headers. Entity interceptors are mainly concerned with marshaling and unmarshaling of HTTP message bodies.

Filters can be configured on the client and server, giving us four extension points for filters, defined by four interfaces:

  • ClientRequestFilter

  • ClientResponseFilter

  • ContainerRequestFilter

  • ContainerResponseFilter

As the names indicate, ClientRequestFilter and ClientResponseFilter are client-side filters, and ContainerRequestFilter and ContainerResponseFilter are server-side filters. Similarly, ClientRequestFilter and ContainerRequestFilter operate on the request, and ContainerResponseFilter and ClientResponseFilter operate on the response.

The client-side or server-side filters may be implemented by the same class or different classes:

public class ClientLoggingFilter implements ClientRequestFilter, 
                                            ClientResponseFilter {
  @Override
  public void filter(ClientRequestContext crc) throws IOException {
    String method = crc.getMethod();
    String uri = crc.getUri();
    for (Entry e : crc.getHeaders().entrySet()) {
      ... = e.getKey();
      ... = e.getValue();
    }
  }

  @Override
  public void filter(ClientRequestContext crc, ClientResponseContext crc1) 
                throws IOException {
    for (Entry e : crc1.getHeaders().entrySet()) {
      ... = e.getKey();
      ... = e.getValue();
    }
  }
}

This code shows a simple client-side filter that will log the headers sent as part of the request and received in the response message. ClientRequestContext provides request-specific information for the filter, such as the request URI, message headers, and message entity or request-scoped properties. ClientResponseContext provides response-specific information for the filter, such as message headers, the message entity, or response-scoped properties.

On the client side, you can register this filter using the client-side API:

Client client = ClientBuilder.newClient();
client.register(ClientLoggingFilter.class);
WebTarget target = client.target(...);

A server-side filter that will log the headers received as part of the request and sent in the response message can be implemented similarly:

@Provider
public class ServerLoggingFilter implements ContainerRequestFilter, 
                                            ContainerResponseFilter {
  @Override
  public void filter(ContainerRequestContext crc) throws IOException {
    String method = crc.getMethod();
    String uri = crc.getUriInfo().getAbsolutePath();
    for (String key : crc.getHeaders().keySet()) {
      ... = key;
      ... = crc.getHeaders().get(key);
    }
  }

  @Override
  public void filter(ContainerRequestContext crc, ContainerResponseContext crc1) 
                throws IOException {
    for (String key : crc1.getHeaders().keySet()) {
      ...  = key;
      .... = crc1.getHeaders().get(key);
    }
  }
}

In this code, the filter is a provider class and thus must be marked with @Provider. This ensures that the filters are automatically discovered. If the filter is not marked with @Provider, it needs to be explicitly registered in the Application class:

@ApplicationPath("webresources")
public class MyApplication extends Application {
  @Override
  public Set<Class<?>> getClasses() {
    Set<Class<?>> resources = new java.util.HashSet<>();
    resources.add(org.sample.filter.ServerLoggingFilter.class);
    return resources;
  }
}

ContainerRequestFilter comes in two flavors: pre-match and post-match. A pre-match filter is applied globally on all resources before the resource is matched with the incoming HTTP request. A pre-match filter is typically used to update the HTTP method in the request and is capable of altering the matching algorithm. A post-match filter is applied after the resource method has been matched. You can convert any ContainerRequestFilter to a pre-match filter by adding the @PreMatching annotation.

On the server side, the filters can be registered in four different ways:

Globally bound to all resources and all methods in them

By default, if no annotation is specified on the filter, then it is globally bound—that is, it all applies to methods on all resources in an application.

Globally bound to all resources and methods via the meta-annotation @NameBinding

The annotation may be specified on the Application class and then the filter becomes globally enabled on all methods for all resources:

@ApplicationPath("webresources")
@ServerLogged
public class MyApplication extends Application {
  //. . .
}
Statically bound to a specific resource/method via the meta-annotation @NameBinding

A filter may be statically targeted to a resource class or method via the meta-annotation @NameBinding as follows:

@NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ServerLogged {}

This annotation then needs to be specified on the filter implementation and the resource class and/or method:

@Provider
@ServerLogged
public class ServerLoggingFilter implements ContainerRequestFilter, 
                                            ContainerResponseFilter {
  //. . .
}

@Path("orders")
@ServerLogged
public class MyResource {
  //. . .
}

If the annotation is specified on a resource, then the filter is applied to all methods of the resource. If the annotation is specified on a specific resource method, then the filter is applied only when that particular method is invoked.

@NameBinding annotation is ignored on filters marked with @PreMatch.

Dynamically bound to a specific resource/method via DynamicFeature

A non−globally bound filter (i.e., a filter annotated with @NameBinding) can be dynamically bound to a resource or a method within a resource via DynamicFeature. For example, the following feature binds all resource methods in MyResource that are annotated with @GET:

@Provider
public class DynamicServerLogggingFilterFeature implements DynamicFeature {
  @Override
  public void configure(ResourceInfo ri, Configurable c) {
    if (MyResource.class.isAssignableFrom(ri.getResourceClass())
        && ri.getResourceMethod().isAnnotationPresent(GET.class)) {
      c.register(new ServerLoggingFilter());
    }
  }
}

In this code, ServerLoggingFilter is configured on the methods of MyResource marked with the GET annotation. This feature is marked with @Provider and is thus automatically discovered by the JAX-RS runtime. Dynamic binding is ignored on filters marked with @PreMatch.

Multiple filters may be implemented at each extension point and arranged in filter chains. Filters in a chain are sorted based on their priorities and are executed in order. Priorities are defined through the @javax.annotation.Priority annotation and represented by integer numbers. The Priorities class defines built-in priorities for security, decoders/encoders, and more. The default binding priority is Priorities.USER.

The priorities for ClientRequestFilter and ContainerRequestFilter are sorted in ascending order; the lower the number, the higher the priority. The priorities for ContainerResponseFilter and ClientResponseFilter are sorted in descending order; the higher the number, the higher the priority. These rules ensure that response filters are executed in reverse order of request filters.

The priority of ClientLoggingFilter defined previously can be changed as shown:

@Provider
@Priority(Priorities.HEADER_DECORATOR)
public class ClientLoggingFilter implements ClientRequestFilter, 
                                            ClientResponseFilter {
  //. . .
}

Filters are executed without method invocation wrapping (i.e., filters execute in their own silos and do not directly invoke the next filter in the chain). The JAX-RS runtime decides whether to invoke the next filter or not.

ClientRequestFilter and ContainerRequestFilter can stop the execution of their corresponding chains by calling abortWith(Response).

Entity interceptors are mainly concerned with marshaling and unmarshaling of HTTP message bodies. They implement ReaderInterceptor or WriterInterceptor, or both.

WriterInterceptor operates on the outbound request on the client side and on the outbound response on the server side:

public class MyWriterInterceptor implements WriterInterceptor {

  @Override
  public void aroundWriteTo(WriterInterceptorContext wic) 
                throws IOException, WebApplicationException {

    wic.setOutputStream(new FilterOutputStream(wic.getOutputStream()) {

      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            
      @Override
      public void write(int b) throws IOException {
        baos.write(b);
        super.write(b);
      }

      @Override
      public void close() throws IOException {
        System.out.println("MyClientWriterInterceptor --> " + baos.toString());
        super.close();
      }
    });
    wic.proceed();
  }
}

ReaderInterceptor operates on the outbound response on the client side and on the inbound request on the server side:

public class MyReaderInterceptor implements ReaderInterceptor {

  @Override
  public Object aroundReadFrom(ReaderInterceptorContext ric) 
                  throws IOException, WebApplicationException {
    final InputStream old = ric.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int c;
    while ((c = old.read()) != -1) {
      baos.write(c);
    }
    ... = baos.toString();
        
    ric.setInputStream(new ByteArrayInputStream(baos.toByteArray()));
        
    return ric.proceed();
  }
}

As with filters, there is an interceptor chain for each kind of entity interceptor. Entity interceptors in a chain are sorted based on their priorities and are executed in order. Priorities are defined via the @javax.annotation.Priority annotation and represented by integer numbers. The Priorities class defines built-in priorities for security, decoders/encoders, and more. The default binding priority is Priorities.USER. The priorities are sorted in ascending order; the lower the number, the higher the priority.

Filters and entity interceptors may be specified on a client and resource. Figure 4-1 shows the invocation sequence on both client and server.

JAX-RS filters and interceptors sequencing

Figure 4-1. JAX-RS filters and interceptors sequencing

Validation of Resources

Bean Validation 1.1 allows declarative validation of resources. The constraints can be specified in any location in which the JAX-RS binding annotations are allowed, with the exception of constructors and property setters:

@Path("/names")
public class NameResource {
  @NotNull
  @Size(min=1)
  @FormParam("firstName")
  private String firstName;

  @NotNull
  @Size(min=1)
  @FormParam("lastName")
  private String lastName;

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

  @Email
  public String getEmail() {
    return email;
  }

  //. . .
}

In this code:

  • firstName and lastName are fields initialized via injection. These fields cannot be null and must be at least one character.

  • email is a resource class property, and the constraint annotation is specified on the corresponding getter.

You can specify cross-field and cross-property constraints by declaring annotations on the class:

@NotNullAndNonEmptyNames
public class NameResource {
  @FormParam("firstName")
  private String firstName;

  @FormParam("lastName")
  private String lastName;

  //. . .
}

In this code, @NotNullAndNonEmptyNames is a custom constraint that requires the first and last name to be not null and both the names to be at least one character long.

You can map request entity bodies to resource method parameters and validate them by specifying the constraint on the resource method parameter:

public class NameResource {
  @POST
  @Consumes("application/json")
  public void addAge(@NotNull @Min(16) @Max(25)int age) {
    //. . .
  }
}

In this code, the request entity body is mapped to the age parameter and must be not null and between the values of 16 and 25.

Alternatively, if the request entity is mapped to a bean that is decorated with constraint annotations already, then @Valid can be used to trigger the validation:

public class NameResource {
  @POST
  @Consumes
  public void addName(@Valid Name name) {
    //. . .
  }
}

JAX-RS constraint validations are carried out in the Default validation group only. Processing other validation groups is not required.

Get Java EE 7 Essentials now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.