JAX-RS

Goals

Concepts

Library

Dependencies

Preview

Farm JAX-RS Application
@ApplicationPath("/farm/")
public class FarmApplication extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    return ImmutableSet.of(PensResourceService.class);  //implementation of PensResource
  }

}
Farm JAX-RS Resource Endpoint
@Path("pens")
public interface PensResource {

  @GET
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Set<Pen> getPens(@QueryParam("minCapacity") int minCapacity,
      @QueryParam("limit") @DefaultValue("-1") int limit) throws IOException, WebApplicationException;

  @HEAD
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public boolean hasPen(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen getPen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @POST
  @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}")
  @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen updatePen(@Nonnull @PathParam("penId") String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  @DELETE
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public void deletePen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @GET
  @Path("{penId}/animals/")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Set<Animal> getAnimals(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  @Path("{penId}/animals/{animalId}")
  public Animal getAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}/animals/{animalId}")
  @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Animal addAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  @DELETE
  @Path("{penId}/animals/{animalId}")
  public void removeAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

}

Lesson

A RESTful API requires implementing a server to process the HTTP requests. When REST was a new concept, the first tool at hand for creating REST servers in Java were servlets—and indeed current solutions are still based on servlets. But when the Java API for RESTful Web Services (JAX-RS) was released, it greatly simplified RESTful server implementation. JAX-RS is a specification for a Java API creating RESTful services, and is part of the Java Platform, Enterprise Edition (Java EE). Besides its interfaces it provides a number of annotations that allow you to declaratively mark classes and methods as endpoints for your RESTful API. The specification is available as the dependency javax.ws.rs-api.

Resource Interface

In JAX-RS you will create a class or an interface to act as the endpoint to a RESTful API request. This endpoint will define your RESTful service.

Using the examples from the lesson on REST, we might make a PensResource interface for providing remote access to managing the pens on a farm.

An interface for a RESTful resource providing access to pens and the animals in them.
/**
 * Interface for the REST resource representing a pen on a farm.
 */
public interface PensResource {

  /**
   * Retrieves all the available pens.
   * @param minCapacity The minimum capacity of pens to return (which could be <code>0</code>).
   * @param limit The maximum number of pens to return, or <code>-1</code> for no limit.
   * @return A set of all available pens.
   * @throws IOException if there is an error accessing the pens.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Set<Pen> getPens(int minCapacity, int limit) throws IOException, WebApplicationException;

  /**
   * Determines whether a given pen exists.
   * @param penId The identifier of a pen.
   * @return true if the given pen exists.
   * @throws IOException if there is an error accessing the pens.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public boolean hasPen(@Nonnull String penId) throws IOException, WebApplicationException;

  /**
   * Retrieves a pen.
   * @param penId The identifier of a pen.
   * @return A representation of the pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws NotFoundException if there is no such pen.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Pen getPen(@Nonnull String penId) throws IOException,
      NotFoundException, WebApplicationException;

  /**
   * Adds a new pen.
   * @param pen A representation of the pen to create.
   * @return A representation of the new pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  /**
   * Updates information about a pen.
   * @param penId The identifier of the pen to be updated.
   * @param pen A representation of the pen to update.
   * @return A representation of the new pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws NotFoundException if there is no such pen.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Pen updatePen(@Nonnull String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  /**
   * Removes a pen.
   * @param penId The identifier of a pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws NotFoundException if there is no such pen.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public void deletePen(@Nonnull String penId) throws IOException,
      NotFoundException, WebApplicationException;

  /**
   * Retrieves all the animals in a given pen.
   * @param penId The identifier of a pen.
   * @return A set of all animals in a given pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Set<Animal> getAnimals(@Nonnull String penId) throws IOException, WebApplicationException;

  /**
   * Retrieves a specific animals from a given pen.
   * @param penId The identifier of a pen.
   * @param animalId The identifier of the animal to retrieve.
   * @return The identified animal.
   * @throws IOException if there is an error accessing the pens.
   * @throws NotFoundException if there is no such pen, or no such animal in the pen.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Animal getAnimal(@Nonnull String penId,
      @Nonnull String animalId) throws IOException, WebApplicationException;

  /**
   * Adds an animal to a given pen.
   * @param penId The identifier of a pen.
   * @param animalId The identifier of the animal to add to the pen.
   * @param animal The animal to add to the pen.
   * @return The animal added to the pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Animal addAnimal(@Nonnull String penId,
      @Nonnull String animalId, @Nonnull Animal animal) throws IOException, WebApplicationException;

  /**
   * Removes an animal from a given pen.
   * @param penId The identifier of a pen.
   * @param animalId The identifier of the animal to remove.
   * @return The animal removed from the pen.
   * @throws IOException if there is an error accessing the pens.
   * @throws NotFoundException if there is no such pen, or no such animal in the pen.
   * @throws WebApplicationException if there was a RESTful API error.
   */
  public Animal removeAnimal(@Nonnull String penId,
      @Nonnull String animalId) throws IOException, WebApplicationException;

}

Binding URI Paths

You will need to indicate to JAX-RS what path your resource interface will be servicing. JAX-RS provides the javax.ws.rs.Path annotation to indicate the URI path(s) the implementation will service. The @Path annotation may contain template parameters to indicate that that part of the path will change based upon which resource is being accessed. Closely related is the javax.ws.rs.PathParam annotation which binds a method parameter to one of the URI template parameters specified by @Path.

Binding URI paths to RESTful resource interface methods.
@Path("pens")
public interface PensResource {

  public Set<Pen> getPens(int minCapacity, int limit) throws IOException, WebApplicationException;

  @Path("{penId}")
  public boolean hasPen(@Nonnull String penId) throws IOException, WebApplicationException;

  @Path("{penId}")
  public Pen getPen(@Nonnull String penId) throws IOException,
      NotFoundException, WebApplicationException;

  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  @Path("{penId}")
  public Pen updatePen(@Nonnull String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  @Path("{penId}")
  public void deletePen(@Nonnull String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @Path("{penId}/animals")
  public Set<Animal> getAnimals(@Nonnull String penId) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal getAnimal(@Nonnull String penId,
      @Nonnull String animalId) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal addAnimal(@Nonnull String penId,
      @Nonnull String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal removeAnimal(@Nonnull String penId,
      @Nonnull String animalId) throws IOException, WebApplicationException;

}

Assuming that the JAX-RS application (more on this later) is mapped to the base path /farm/ in the root of the servlet container, the annotation @Path("pens") will map the resource to the /farm/pens URI path. Methods that have no further @Path designation such as getPens() will also be mapped to this base path. If a method has a further @Path designation, it will be considered relative to the resource path. For example the getAnimals(…) method will be mapped to /farm/pens/{penId}.

URI Path Templates

Path designations are templates and may contain replacement variables. Most common is simple placing the variable name between curly brackets { and }. The string /farm/pens/{penId} for example indicates that the {penId} part of the path, identified by the replacement variable penId, will depend on the value passed in the actual URI of the request.

The javax.ws.rs.PathParam annotation binds a method parameter to one of the URI template parameters specified by @Path. The designated variables in the path template are referred to using the @PathParam annotating a method parameter. JAX-RS will automatically extract the relevant replacement values for the indicated variables, and make them available as arguments when it calls the method! For example if a user access a URI with the path /farm/pens/pigpen, the ID pigpen would be passed to the getAnimals(…) method as the argument to the penId method parameter.

Mapping path parameters to Java method parameters.
@Path("pens")
public interface PensResource {

  public Set<Pen> getPens(int minCapacity, int limit) throws IOException, WebApplicationException;

  @Path("{penId}")
  public boolean hasPen(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @Path("{penId}")
  public Pen getPen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  @Path("{penId}")
  public Pen updatePen(@Nonnull @PathParam("penId") String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  @Path("{penId}")
  public void deletePen(@Nonnull @PathParam("penId) String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @Path("{penId}/animals")
  public Set<Animal> getAnimals(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal getAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal addAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  @Path("{penId}/animals/{animalId}")
  public Animal removeAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

}

Indicating URI Query Parameters

As you experienced when documenting your RESTful API using the OpenAPI, some parameters that specify how values should be retrieved may not appear as variables in the URI path but as URI query parameters. JAX-RS provides the javax.ws.rs.QueryParam annotation for mapping URI query parameters to Java method parameters. In conjunction with the optional javax.ws.rs.DefaultValue annotation, @QueryParam names the query parameter that corresponds to the Java parameter variable. Java knows how to convert query parameter string values to Java primitive types if needed.

Mapping URI query parameters to Java method parameters.
@Path("pens")
public interface PensResource {

  public Set<Pen> getPens(@QueryParam("minCapacity") int minCapacity,
      @QueryParam("limit") @DefaultValue("-1") int limit) throws IOException, WebApplicationException;

  …

Specifying HTTP Methods

Now that you've indicated which paths are mapped to which methods and to the resource, you need to specify which methods respond to which HTTP methods. This is as easy as using annotations such as javax.ws.rs.GET. JAX-RS provides annotations corresponding to most of the methods you're familiar with are available, including @GET, @HEAD, @PUT, @DELETE, and @POST. In fact JAX-RS will implement support for the HTTP HEAD method by default; you only need to use @HEAD if you want the implementation to be more efficient.

Indicating HTTP methods for a RESTful API using JAX-RS.
@Path("pens")
public interface PensResource {

  @GET
  public Set<Pen> getPens(@QueryParam("minCapacity") int minCapacity,
      @QueryParam("limit") @DefaultValue("-1") int limit) throws IOException, WebApplicationException;

  @HEAD
  @Path("{penId}")
  public boolean hasPen(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Path("{penId}")
  public Pen getPen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @POST
  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}")
  public Pen updatePen(@Nonnull @PathParam("penId") String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  @DELETE
  @Path("{penId}")
  public void deletePen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @GET
  @Path("{penId}/animals")
  public Set<Animal> getAnimals(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Path("{penId}/animals/{animalId}")
  public Animal getAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}/animals/{animalId}")
  public Animal addAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  @DELETE
  @Path("{penId}/animals/{animalId}")
  public Animal removeAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

}

To put the cow Bessie in the pigpen, for example, this interface would instruct JAX-RS to allow a client to issue a PUT to /farm/pens/pigpen/animals/bessie, sending a representation of Bessie in the content of the HTTP request. (See below.)

Content Negotiation

Producing Content

JAX-RS will automatically perform content negotiation with the HTTP client, and based upon the request Accept header will call the correct method based upon the media type specified in the javax.ws.rs.Produces annotation. If you are able to produce resource representations, you could create separate methods e.g. getPensText() and getPensJSON() and annotate them individually with @Produces("text/plain; charset=UTF-8") and @Produces("application/json; charset=UTF-8"), respectively. If you decide to handle both types based upon the Accept header, you could annotation a single method with @Produces({"text/plain; charset=UTF-8", "application/json; charset=UTF-8"}), as shown below. The first content type listed will be the default if the client does not send an Accept header. You can marshall (generate resource representations for transfer from one place to another) many media types automatically with a third-party library such as Jackson, as explained below.

Supporting negotiation for returned content media types.
@Path("pens")
public interface PensResource {

  @GET
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Set<Pen> getPens(@QueryParam("minCapacity") int minCapacity,
      @QueryParam("limit") @DefaultValue("-1") int limit) throws IOException, WebApplicationException;

  @HEAD
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public boolean hasPen(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen getPen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @POST
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen addPen(@Nonnull Pen pen) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Pen updatePen(@Nonnull @PathParam("penId") String penId,
      @Nonnull Pen pen) throws IOException, NotFoundException, WebApplicationException;

  @DELETE
  @Path("{penId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public void deletePen(@Nonnull @PathParam("penId") String penId) throws IOException,
      NotFoundException, WebApplicationException;

  @GET
  @Path("{penId}/animals")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Set<Animal> getAnimals(@Nonnull @PathParam("penId") String penId) throws IOException, WebApplicationException;

  @GET
  @Path("{penId}/animals/{animalId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Animal getAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

  @PUT
  @Path("{penId}/animals/{animalId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Animal addAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  @DELETE
  @Path("{penId}/animals/{animalId}")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Animal removeAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId) throws IOException, WebApplicationException;

}

If you want finer control over exactly what sort of response is returned, you can make your method signature return a javax.ws.rs.core.Response object. Working with a Response instance is facilitated through use of the javax.ws.rs.core.Response.ResponseBuilder class.

  …

  @Override
  public Response getAnimal(@Nonnull String penId,
      @Nonnull String animalId) throws IOException, WebApplicationException {
    //TODO get animal
    final ResponseBuilder responseBuilder = Response.ok(animal);
    responseBuilder.header("Foo", "bar");	//set a custom header
    return responseBuilder.build();
  }

  …
Consuming Content

Your RESTful API may not only produce content in response to queries, it may also consume content, in response to the HTTP PUT method for instance. The particular media type(s) consumed is indicated using the javax.ws.rs.Consumes annotation. Here as well you consume many media types automatically with a third-party library such as Jackson, as explained below.

Specifying types of content accepted by a RESTful resource.
  …

  @PUT
  @Path("{penId}//animals/{animalId}")
  @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
  @Produces({MediaType.TEXT_PLAIN + "; charset=UTF-8", MediaType.APPLICATION_JSON + "; charset=UTF-8"})
  public Animal addAnimal(@Nonnull @PathParam("penId") String penId,
      @Nonnull @PathParam("animalId") String animalId,
      @Nonnull Animal animal) throws IOException, WebApplicationException;

  …

Resource Services Implementation

The PenResource interface only defines the RESTful API for Java, much as your OpenAPI specification of your RESTful interface described it for developers. You will still need to implement your RESTful resource interface. In your implementation you simply need to indicate your resource interface you are implementing; you do not need to repeat its JAX-RS annotations.

Example implementation of a PensResource.
public class PensResourceService implements PensResource {

  private final Map<String, Pen> pens = new ConcurrentHashMap<>();

  private final AtomicLong nextPenNumber = new AtomicLong(10000L);

  …

  public PensResourceService() {
    pens.put("pigpen", new Pen("pigpen", "Pig Pen", 40, 30, 100));
    pens.put("pen1432", new Pen("1432", "Cattle Yard", 10, 20, 50));
  }

  @Override
  public Set<Pen> getPens(final int minCapacity, final int limit) throws IOException, WebApplicationException {
    return new HashSet<>(pens.values());
  }

  @Override
  public boolean hasPen(final String penId) throws IOException, WebApplicationException {
    return pens.containsKey(requireNonNull(penId));
  }

  @Override
  public Pen getPen(final String penId) throws IOException, NotFoundException, WebApplicationException {
    final Pen pen = pens.get(requireNonNull(penId));
    if(pen == null) {
      throw new NotFoundException();
    }
    return pen;
  }

  @Override
  public Pen addPen(final Pen pen) throws IOException, WebApplicationException {
    final String newPenId = "pen" + nextPenNumber.getAndIncrement();
    //copy the values from the existing pen, using the generated ID
    final Pen newPen = new Pen(newPenId, pen.getName(), pen.getLength(), pen.getWidth(), pen.getCapacity());
    pens.put(newPenId, newPen);
    return newPen;
  }

  @Override
  public Pen updatePen(final String penId, final Pen pen) throws IOException, NotFoundException, WebApplicationException {
    if(!pens.containsKey(requireNonNull(penId))) {
      throw new NotFoundException();
    }
    //small race condition here; possible to add a pen if deleted after check
    pens.put(penId, pen);
    return pen;
  }

  @Override
  public void deletePen(final String penId) throws IOException, NotFoundException, WebApplicationException {
    final Pen deletedPen = pens.remove(requireNonNull(penId));
    if(deletedPen == null) {
      throw new NotFoundException();
    }
  }

  …

}

Application

You also need to create a class to represent your set of RESTful services, extending the javax.ws.rs.core.Application class. You will annotate your application class with javax.ws.rs.ApplicationPath to indicate the base URI path for your RESTful API. The Application.getClasses() method will return a set of classes you want JAX-RS to instantiate as needed. This is how you register the various classes you've defined, and will include things like:

@ApplicationPath("/farm/")
public class FarmApplication extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    return ImmutableSet.of(PensResourceService.class);  //implementation of PensResource
  }
}

If you are using a JAX-RS aware servlet container, that's all you need to do! The container will discover your JAX-RS application class; instantiated; mount it at the indicated URI path inside the container; and register its service implementations and related classes. If your servlet container is not JAX-RS aware, your JAX-RS implementation (e.g. RESTEasy) may provide an initializer library to provide this functionality.

Security

TODO talk briefly about @RolesAllowed and indicate that configuration is container-specific

RESTEasy

The JAX-RS implementation you will be using is RESTEasy from JBoss. Although RESTEasy was made to run with the JBoss WildFly application server, RESTEasy also works as a standalone JAX-RS implementation inside any servlet container. If your servlet container is JAX-RS aware, no further configuration is needed other than your JAX-RS application and related classes. For non-JAX-RS aware servlet containers that nevertheless support the latest servlet specification, RESTEasy provides the resteasy-servlet-initializer library. Include it in your project, and your servlet container will find your JAX-RS application implementation automatically.

Content

Manual Content Processing

If you want to produce content manually, you can return an instance of javax.ws.rs.core.StreamingOutput from your method. You can also consume or produce content by adding one of the following types as a method signature or as a return type, respectively:

Custom Marshalling

Rather than dealing with low-level streams, it would be better to use specific provider strategy implementations for custom marshalling. JAX-RS provides the javax.ws.rs.ext.MessageBodyReader<T> and javax.ws.rs.ext.MessageBodyWriter<T> interfaces with many options for you to indicate in your implementation which media type(s) and Java type(s) you support for reading and writing. The implementation will be annotated with javax.ws.rs.ext.Provider. Besides the @Provider annotation, a MessageBodyReader implementation will be annotated with @Consumes(…), while a MessageBodyWriter implementation will be annotated with @Produces(…). If you implement one of these providers, you will need to register it with the JAX-RS application as you did with your resource implementation(s).

The following example shows how you could write a custom message body writer for returning a list of objects as string representations of the list items, each on a separate line. Once this class is registered with JAX-RS, any resource implementation returning a Set<E> with an indicated @Produces type of "text/plain" will use this MessageBodyWriter to return content.

Custom text/plain marshalling of a Set<E>.
@Provider
@Produces(MediaType.TEXT_PLAIN + "; charset=UTF-8") // TODO use constant
public class PlainTextLinesMessageBodyWriter implements MessageBodyWriter<Set<?>> {

  @Override
  public boolean isWriteable(final Class<?> type, final Type genericType,
      final Annotation[] annotations, final MediaType mediaType) {
    return Set.class.isAssignableFrom(type) && mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE);
  }

  @Override
  public long getSize(final Set<?> set, final Class<?> type, final Type genericType,
      final Annotation[] annotations, final MediaType mediaType) {
    return -1;  //this method is now deprecated
  }

  @Override
  public void writeTo(final Set<?> set, final Class<?> type, final Type genericType,
      final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
      final OutputStream entityStream) throws IOException, WebApplicationException {
    String charset = mediaType.getParameters().get("charset");
    if (charset == null) {
      charset = StandardCharsets.UTF_8.name();  //default to UTF-8
    }
    final Writer writer = new BufferedWriter(new OutputStreamWriter(entityStream, charset));
    for (final Object element : set) {
      writer.append(element.toString()).append('\n');
    }
    writer.flush();  //make sure any buffered content is written, but do not close the writer
  }

}
@ApplicationPath("/farm/")
public class FarmApplication extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    return ImmutableSet.of(PensResourceService.class, PlainTextLinesMessageBodyWriter.class);
  }
}

Jackson

You have already used the Jackson library for processing JSON. RESTEasy provides a convenient way to plug JSON marshalling providers into your JAX-RS application using the resteasy-jackson2-provider library. Including this library in your project should automatically provide marshalling for application/json response content types that follow JavaBeans POJO conventions. For consuming HTTP requests in JSON, you can parse the JSON tree as you did in a previous lesson, or implement a Jackson mapper as explained in the Jackson documentation, provided in the Resources section. If you decide to parse a JSON data stream directly, it would be best to place that implementation in a separate provider for your type, as explained above.

Imagine for example that you have the following implementation of Pen. When an HTTP client retrieves the pens, Jackson will produce JSON output similar to that shown below.

Example implementation of Pen and corresponding Jackson JSON output from /farm/pens/.
public class Pen {

  private final String id;
  private final String name;
  private final int length;
  private final int width;
  private final int capacity;

  public Pen(@Nonnull final String id, @Nonnull final String name, final int length, final int width, final int capacity) {
    this.id = requireNonNull(id);
    this.name = requireNonNull(name);
    this.length = length;
    this.width = width;
    this.capacity = capacity;
  }

  public String getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public int getLength() {
    return length;
  }

  public int getWidth() {
    return width;
  }

  public int getCapacity() {
    return capacity;
  }

  @Override
  public int hashCode() {
    return getId().hashCode();
  }

  @Override
  public boolean equals(final Object object) {
    if(this == object) {
      return true;
    }
    if(!(object instanceof Pen)) {
      return false;
    }
    return getId().equals(((Pen)object).getId());
  }

  @Override
  public String toString() {
    return getId();
  }
}
[
  {
    "id": "pigpen",
    "name": "Pig Pen",
    "length": 40,
    "width": 30,
    "capacity": 100
  },
  {
    "id": "1432",
    "name": "Cattle Yard",
    "length": 10,
    "width": 20,
    "capacity": 50
  }
]

Review

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Create a Booker JAX-RS application matching your RESTful API documentation, serving information from a PublicationRepository on the backend with Guice injection.

See Also

References

Resources

Acknowledgments