JAX-RS - Handling 'Last-Modified' and 'ETag' headers with Request Injection

[Updated: Sep 11, 2017, Created: Sep 10, 2017]

An instance of javax.ws.rs.core.Request can be injected as field or method parameter using the @Context annotation. Request is a helper object which can be used to evaluate preconditions related to HTTP request headers. Following methods of Request can be used to check the preconditions for 'Last-Modified' and 'ETag' headers:

Response.ResponseBuilder evaluatePreconditions(EntityTag eTag)

'ETag' HTTP header is meant to utilize browser cache effectively. This method checks if the provided eTag value matches with the request's 'If-None-Match' header value. If it does then this method returns non-null ResponseBuilder with 304 status code and empty body, otherwise, user is responsible to prepare the response with 200 status and a new value of 'ETag' header. check out this to have basic understanding how 'ETag' works.



Response.ResponseBuilder evaluatePreconditions(Date lastModified)

'Last-Modified' HTTP header is also meant to utilize browser cache effectively. This method is used to check if provided lastModified value matches the 'If-Modified-Since' header value. If it does then this method returns non-null ResponseBuilder with 304 status code and empty body, otherwise, user is responsible to prepare the response with 200 status and a new value of 'Last-Modified' header. check out this to have basic understanding how 'Last-Modified' works.

Example

A JAX-RS resource injecting Request

@Path("/users")
public class UserResource {

  @GET
  public Response getUsers(@Context Request request) {
      Date lastModifiedDate = UserService.Instance.getUsersLastModifiedDate();
      Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate);
      if (responseBuilder == null) {//last modified date didn't match, send new content
          return Response.ok("dummy user list")
                         .lastModified(lastModifiedDate)
                         .build();
      } else {
          //sending 304 not modified
          return responseBuilder.build();
      }
  }

  @GET
  @Path("{id}")
  public Response getUser(@PathParam("id") String id, @Context Request request) {
      EntityTag eTag = UserService.Instance.getETagForUser(id);
      Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag);
      if (responseBuilder == null) {//eTag has changed, sending new content
          return Response.ok(UserService.Instance.getUser(id))
                         .tag(eTag)
                         .build();
      } else {
          //sending 304 not modified
          return responseBuilder.build();
      }
  }
}
public enum UserService {
  Instance;

  public Date getUsersLastModifiedDate() {
      return Date.from(LocalDate.now()
                                .minusDays(21).atStartOfDay()
                                .atZone(ZoneId.systemDefault()).toInstant());
  }

  public EntityTag getETagForUser(String userId) {
      return new EntityTag("version1");
  }

  public Object getUser(String id) {
      return "dummy user for id: " + id;
  }
}

Output

Accessing '/users'

First access to '/users'

Checking request/response headers in google's Developer Tools:

Subsequent access to '/users'

If Last-Modified value changes on the server then server will send 200 status with new body and new 'Last-Modified' response header.

Accessing '/users/{id}'

First access:

Subsequent access:

If ETag value changes on the server then server will send 200 status with new body and new 'ETag' response header.

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.
  • JDK 1.8
  • Maven 3.3.9

@Context Request Example Select All Download
  • injecting-request
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also