JAX-RS
Goals
- Explore the JAX-RS framework.
- Implement a RESTful API using JAX-RS.
Concepts
- marshall
- Java API for RESTful Web Services (JAX-RS)
- Java Platform, Enterprise Edition (Java EE)
- provider
- RESTEasy
Library
javax.servlet.http.HttpServletResponse
javax.ws.rs
javax.ws.rs.ApplicationPath
javax.ws.rs.ClientErrorException
javax.ws.rs.Consumes
javax.ws.rs.DefaultValue
javax.ws.rs.DELETE
javax.ws.rs.HEAD
javax.ws.rs.GET
javax.ws.rs.NotFoundException
javax.ws.rs.Path
javax.ws.rs.PathParam
javax.ws.rs.Produces
javax.ws.rs.POST
javax.ws.rs.PUT
javax.ws.rs.QueryParam
javax.ws.rs.RedirectionException
javax.ws.rs.ServerErrorException
javax.ws.rs.WebApplicationException
javax.ws.rs.core.Application
javax.ws.rs.core.Application.getClasses()
javax.ws.rs.core.Application.getSingletons()
javax.ws.rs.core.Context
javax.ws.rs.core.MediaType
javax.ws.rs.core.Response
javax.ws.rs.core.Response.ResponseBuilder
javax.ws.rs.core.StreamingOutput
javax.ws.rs.ext.MessageBodyReader<T>
javax.ws.rs.ext.MessageBodyWriter<T>
javax.ws.rs.ext.Provider
Dependencies
javax.ws.rs:javax.ws.rs-api:2.0.1
(scope: provided)org.jboss.resteasy:resteasy-jaxrs:3.1.0.CR3
org.jboss.resteasy:resteasy-servlet-initializer:3.1.0.CR3
org.jboss.resteasy:resteasy-jackson2-provider:3.1.0.CR3
Preview
@ApplicationPath("/farm/")
public class FarmApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return ImmutableSet.of(PensResourceService.class); //implementation of PensResource
}
}
@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.
/**
* 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
.
@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.
@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.
@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.
@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.
@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.
…
@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.
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:
- Resource class implementations.
- Marshalling providers (discussed below)
- Exception mappers (discussed below)
@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:
java.io.InputStream
java.io.Reader
byte[]
String
char[]
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.
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.
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
- If you don't indicate a default charset in your
@Produces
declaration, there is no guarantee which charset the browser will assume. - Don't forget to add your resource implementation class to those returned by your application's
getClasses()
method, or JAX-RS will not know about your resource service and nothing will appear at its associated URI path.
In the Real World
- TODO
Think About It
- TODO
Self Evaluation
- Which JAX-RS annotation would you use to inject values into your RESTful implementation?
Task
Create a Booker JAX-RS application matching your RESTful API documentation, serving information from a PublicationRepository
on the backend with Guice injection.
- Create RESTful resource interface(s) matching the Booker RESTful API you created, placing them in a
….booker.server.rest.resources
package. - Implement your RESTful API using service(s) in a
….booker.server.rest.services
package. Allow full CRUD operations using JSON as a marshalling format. - If you need to implement any providers, place them in a
….booker.server.rest.providers
package. - Use dependency injection to provide resource services that delegate the appropriate
PublicationRepository
. For now wire up your in-memoryFakePublicationRepository
as the implementation, initialized with the contents of yourSnapshotPublicationRepository
. In order to use dependency injection, you'll want to return singleton instances in your REST application rather than implementation classes. - Test the RESTful API using Postman.
- Deploy your RESTful implementation. Your server should now support:
/booker/api/
- A full implementation of your RESTful API.
/booker/api-doc/
- OpenAPI documentation of your RESTful API.
See Also
- Building RESTful Web Services with JAX-RS (Oracle - The Java EE Tutorial)
- RESTful Java with JAX-RS 2.0 (Bill Burke - O'Reilly Media)
- Jackson in Five Minutes
References
- RFC 6570: URI Template
Resources
- Java API for RESTful Services (JAX-RS) (Java.net)
- RESTEasy (JBoss)
- RESTEasy Documentation (JBoss)
- Jackson (GitHub)
- Jackson JSON Processor Wiki
Acknowledgments
- Some symbols are from Font Awesome by Dave Gandy.