An Introduction to JAX-RS and Jersey
By Brian Gilstrap, OCI Principal Software Engineer
August 2009
Introduction
If you aren't already involved in building RESTful web services, you may not be aware of JSR 311. JSR 311 is "JAX-RS: The JavaTM API for RESTful Web Services". Its goal is to "develop an API for providing support for REST-ful (REpresentational State Transfer) Web Services in the Java Platform". If you aren't familiar with REST, you may want to read the Wikipedia page that describes REST. That page also has good links to other online resources regarding REST. In short, REST is an architectural approach to building robust, easy-to-use web services that are well-connected to themselves and easy to connect to each other. It follows the basic paradigm of the world wide web as you browse it every day, and is a powerful approach to building web services.
The JAX-RS specification describes an API for developers that greatly simplifies the process of building (RESTful) web services. The fundamental idea behind JAX-RS is that developers spend little or no time marshalling and unmarshalling requests and responses, and instead build Plain Old Java Objects (POJOs) for individual resources and for collections of resources. The JAX-RS implementation coordinates with the web server or container to make those resources available via URLs and to convert the HTTP requests into Java objects, and to convert Java object resource representations into HTTP responses. The reference implementation for JAX-RS is called "Jersey" and the project is hosted on the dev.java.net site. Jersey itself is implemented for Java 5 (and later) and can be integrated with a number of different containers and HTTP servers.
A particularly compelling way to use Jersey is to combine it with the Project Grizzly HTTP server. Project Grizzly provides a high performance HTTP server, and it makes building standalone web services amazingly easy. All of the examples presented here use this approach. However, Jersey can be just as well be integrated with many other web containers, including just about any standard servlet container or J2EE environment, such as Tomcat, WebLogic, or WebSphere.
In addition to the server-side API and implementation, Jersey provides a client API, used to write clients of RESTful Web Services. The client API is also used by service implementations which are themselves clients of other RESTful services.
Jersey includes support for XML serialization of JAXB beans, ATOM syndication feeds, and the use of JSON as a wire format for request and response entities. It integrates with the Spring IoC container, JavaMail, and the Java Activation Framework. It is hosted in a Maven repository, but can be used without using Maven.
Getting Started with Jersey
If you use Maven you can use it to download Jersey and keep it up to date. There are links to the POM files on the Jersey getting started page. In particular, you need the jersey-server module, and the grizzly-servlet-webserver module. If you are not using Maven, you need only a few JARs:
[NOTE: Jersey and Grizzly are under active development, so you will probably find newer versions of these jars available]
Once you have the JARs (and optionally the source and javadoc jars to go with each), configure your classpath or your IDE to include them in your project. You're then ready to write the obligatory 'hello world' program.
Jersey also supports describing services with the Web Application Description Language (WADL), a service description language similar to WSDL. If you want to use WADL with JDK 1.5, you need the jaxb-impl module. If you aren't using Maven and want to use WADL with JDK 1.5, you need some additional jars:
We ignore WADL in this discussion, which limits the required code jars to just the first five.
Note: You can download a JAR containing all the sources (plus more) for the programs described in this article. See the references section below.
A JAX-RS and Jersey Hello World
So, what is the simplest, smallest "hello world" we can implement with JAX-RS? The Jersey site provides an example and it's surprisingly small. We will start with an example almost identical to the one from the Jersey documentation and grow from there.
Ignoring our package statements and imports, there are two files of about ten lines each. The first one is our main class that starts up the service and generally gets things going. We keep this class separate because it is essentially a very small bit of bootstrap code and is almost identical for each service (if you like to use Spring or Guice, you could use them to do the same thing). Here's the class in its entirety:
- package net.gilstraps.server;
-
- import com.sun.grizzly.http.SelectorThread;
- import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- public class Main {
- public static void main(String[] args) throws IOException {
- Map<String,String> initParams = new HashMap<String, String>();
- initParams.put("com.sun.jersey.config.property.packages","net.gilstraps.server");
- SelectorThread selector =
- GrizzlyWebContainerFactory.create("http://localhost:9998/",initParams);
- //noinspection ResultOfMethodCallIgnored
- System.in.read(); // Wait for input before quitting.
- selector.stopEndpoint();
- System.exit(0);
- }
- }
The code here is pretty easy to understand, so let's look at it. These two lines are a crucial part of configuring Jersey:
- Map<String,String> initParams = new HashMap<String,String>();
- initParams.put("com.sun.jersey.config.property.packages","net.gilstraps.server");
With these lines, we tell Jersey which Java packages should be examined at runtime to find classes which implement RESTful resources. We've specified that Jersey should look at the "net.gilstraps.server" package and examine the classes in that package to find ones which have JAX-RS annotations on them. Those annotations tell a JAX-RS implementation what resources each class serves up to clients and what URI's those resources have. There are also annotations for specifying which HTTP methods match up to which Java methods. We'll look at these shortly.
The next line instantiates the Grizzly HTTP server, telling it the base URI for all the URLs it should support and providing it with initialization parameters:
- SelectorThread selector = GrizzlyWebContainerFactory.create(
- "http://localhost:9998/",
- initParams );
The call returns a SelectorThread which represents the control object for the HTTP server. With the string "http://localhost:9998/"
, we are telling the service to use port 9998 of the host named 'localhost'. Grizzly has built-in knowledge of Jersey and our inclusion of the "com.sun.jersey.config.property.packages"
parameter tells Grizzly to start up Jersey.
The remaining lines provide a simple means to shut down the service gracefully. The program reads input from System.in
and then shuts things down cleanly. If you run the program on a command line you can shut it down without having to kill it by simply typing return:
- System.in.read();
- selector.stopEndpoint();
- System.exit(0);
So, where is our cliched "hello world" resource? There is one more class used to implement our service, also quite small:
- package net.gilstraps.server;
-
- import javax.ws.rs.Produces;
- import javax.ws.rs.GET;
- import javax.ws.rs.Path;
- import javax.swing.text.html.HTML;
-
- @Path("/helloworld")
- public class HelloWorldResource {
- @GET @Produces("text/plain")
- public String getClichedMessage() {
- return "Hello Stage 1";
- }
- }
This class is almost a POJO. It doesn't implement a particular interface, or extend some framework class. It has a very simple method ('getClichedMessage') that returns a regular Java string. How can this implement a "hello world" web service? The key to making it a JAX-RS resource class is the annotations applied to it. Let's examine each one:
@Path
The @Path
annotation, when applied to a class, tells JAX-RS what URI or set of URIs the class serves. In this case, we are specifying just the URI of "/helloworld"
. Thus, with the base URI of "http://localhost:9998"
we would access this resource via the URL "http://localhost:9998/helloworld"
.
@GET
The @GET
annotation on a method tells JAX-RS that the Java method implements HTTP's GET
method (the method your browser uses every time you visit a web page). So JAX-RS now knows that a client doing a GET
of "http://localhost:9998/helloworld"
should result in the getClichedMessage
method being invoked.
@Produces
The @Produces
annotation tells JAX-RS the MIME type of the resource returned by getClichedMessage
, which in this case is text/plain - the MIME type for a plain text document. We've aimed for a minimal service here, so we're going for the simplest type of resource we can. It's also possible to return other kinds of entities as the result of a request, and it's even possible to use separate conversion classes to convert different kinds of Java objects into particular kinds of response entities (like a JPEG image, for example). But this service just returns a text document.
Running the example
At this point, JAX-RS knows everything it needs to support a client doing a GET on the resource identified by the URI "http://localhost:9998/helloworld"
. When we compile and run our service, it doesn't output anything at first. But if we open a browser and navigate to the URL (causing the browser to perform an HTTP GET
), we see some output from Grizzly/Jersey:
Jun 12, 2009 9:02:51 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
net.gilstraps.server
Jun 12, 2009 9:02:51 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Root resource classes found:
class net.gilstraps.server.HelloWorldResource
Jun 12, 2009 9:02:51 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Provider classes found:
From this we can see that Grizzly brings Jersey in to handle the request we made with the browser. Jersey then scans for classes which serve up REST resources. It tells us that it found our class:
Jun 12, 2009 9:02:51 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Root resource classes found:
class net.gilstraps.server.HelloWorldResource
Even more interesting, the browser where we made the GET
request gets the string that was returned from getClichedMessage
as the result of its GET
request:
And that's a simple 'hello world' implemented in JAX-RS using Jersey and Grizzly.
Returning More than one Representation of a Resource
One of the principles of REST is that a client makes a request for a representation of a resource. The resource itself is an abstract concept of some idea or real-world object, just as an object-oriented model is an abstraction. But in the case of a RESTful web service the set of methods is limited while the set of representations of the resources (objects) can be much broader. Because REST has only a few methods (verbs), it is common to offer different representations of a resource, returning a particular representation based upon what the client requests.
As a simple example, imagine we are aficionados of military planes and decide to create a web service which provides non-secret information (and perhaps speculation) about various military aircraft (thanks to my friend Bruce for this idea). In the REST world, our resource might be "F-22". In this case the resource is the F-22 fighter plane. But there are many possible representations of an F-22. We might provide a summary description of the plane, or an image of the plane, or a CAD schematic of the plane, or any of a number of other representations. This basic notion that a single resource may have more than representation can be demonstrated via an object that can provide either an HTML text description of the F-22 or a graphical drawing of the plane.
Perhaps the simplest way to provide more than one representation for a given resource is to provide two different methods in a Java class with different MIME types in their @Produces
annotations. In our example, assume we have a class named F22 which provides representations of an F-22. It can have a method that returns HTML and a different method that returns a JPEG image. This is very easy to do with JAX-RS:
- @Path("/planes/f22")
- public class F22 {
- @GET @Produces("text/html")
- public String getHTMLRepresentation() throws IOException {
- File f = new File( "F22.html");
- BufferedReader br = new BufferedReader(new FileReader(f));
- StringWriter sw = new StringWriter();
- for( String s = br.readLine(); s != null ; s = br.readLine() ) {
- sw.write( s );
- sw.write( '\n' );
- }
- return sw.toString();
- }
-
- @GET @Produces("image/jpeg")
- public File getImageRepresentation() {
- return new File( "F22.jpg");
- }
-
- }
We now run into a problem using a web browser for a testing client. The browser is going to specify in its HTTP request the formats it prefers for a response. The browser doesn't allow us to specify the type of representation we want. So, do we get back the HTML representation or the JPEG image? The answer depends upon the web browser you use. Firefox 3.5 on a Mac and Windows, and Safari on a Mac return the HTML form:
IE 6 on Windows and IE 8 on Windows return the JPEG image:
There are other ways to implement support for returning more than one representation. For example, we can implement a single method which returns a JAX-RS Response object. That Response can have different contents depending upon the requested representation.
Response Entity Providers
If you were looking at the example code above, you might have wondered about the implementation of getImageRepresentation
:
- @GET @Produces("image/jpeg")
- public File getImageRepresentation() {
- return new File( "F22.jpg");
- }
This method might seem a bit odd at first sight. The getImageRepresentation
method has a @Produces
of image/jpeg yet it returns a java.io.File
.
This apparent disconnect is resolved by a JAX-RS feature called entity providers. The specification states that entity providers "supply mapping services between representations and their associated Java types". In a nutshell, entity providers take HTTP request entities and turn them into Java objects on the incoming request side, and take Java objects and turn them into response entities on the result side. Entity providers which convert request entities to Java types implement the MessageBodyReader
interface and must be marked with the @Provider
annotation. Similarly, entity providers which convert Java return types to response entities implement the MessageBodyWriter
interface and are also marked with the @Provider
annotation (see below).
JAX-RS mandates a set of built-in entity providers covering common entity types such as strings, input streams, java.io.Reader
objects, java.io.File
objects, and a number of other sophisticated types of conversions.
In this case, Jersey takes the returned File
object and uses its contents as the response entity which it marks as image/jpeg. This mapping from Java types to HTTP entities can be quite powerful, as you don't have to write code to read the contents of files and return them as byte arrays or strings. It also allows a JAX-RS implementation to improve performance. For example, a JAX-RS implementation might choose to use Java's NIO facilities to read the contents of a file when constructing a response entity.
Because of this feature, our implementation method which returns HTML can also be simplified. Rather than opening and reading the contents of the file into memory:
- @GET @Produces("text/html")
- public String getHTMLRepresentation() throws IOException {
- File f = new File( "F22.html");
- BufferedReader br = new BufferedReader(new FileReader(f));
- StringWriter sw = new StringWriter();
- for( String s = br.readLine(); s != null ; s = br.readLine() ) {
- sw.write( s );
- sw.write( '\n' );
- }
- return sw.toString();
- }
We can simply return a File
object and let Jersey read the contents of the file as the response:
- @GET @Produces("text/html")
- public File getHTMLRepresentation() throws IOException {
- return new File( "f22.html");
- }
This notion of entity providers is a crucial aid to the POJO style of JAX-RS applications, and avoids what would otherwise be a great deal of boilerplate code to perform these conversions.
The Jersey Client Framework
Depending upon how you design your RESTful service, you may or may not want to have separate URLs for separate representations of the same resource. This presents a problem when trying to test with a browser. There is no way to tell popular browsers that you want a text/html representation or an image/jpeg representation. The browser has a list of preferred media types, but none that I'm aware of allow you to customize this (either in general or for a particular request). Even more importantly, we need to be able to build solid unit tests for our services. The Jersey client framework provides a good solution to this problem. It is designed to allow developers of RESTful web services to write good unit tests, but is more general purpose than that. It can also be used to write RESTful client applications.
There is an excellent tutorial on the Jersey client API which you should download and read if you plan to use it. But I will give you a taste of the API here.
The Jersey client API is very clean and requires a minimum of fuss to use. You will need the Jersey core jar, and the matching release of the client jar (such as the jersey-client-1.0.3.jar). As an example, let's write a unit test for our web service serving up information about fighter planes. First, we write the code to set up and tear down our service implementation. This code is the same as that in our Main class before, just split up between the setup and tear-down methods.
- public class Test3b {
-
- private SelectorThread selector;
-
- @org.junit.Before
- public void createService() throws IOException {
- Map<String,String> initParams = new HashMap<String,String>();
- initParams.put(
- "com.sun.jersey.config.property.packages",
- "net.gilstraps.server");
- selector = GrizzlyWebContainerFactory.create(
- "http://localhost:9998/", initParams);
- }
-
- @org.junit.After
- public void destroyService() {
- selector.stopEndpoint();
- selector = null;
- }
- // ...
The only difference in this case is we don't read from standard input to shut down the service, since we always want to shut it down at the end of the unit test.
Now we can test that we get the HTML we expect when we invoke the service. First, we do a bit of hoop jumping to read in a copy of the HTML we expect to receive:
- @org.junit.Test
- public void testF22Html() {
- try {
- File expected = new File("f22.html");
- long fileSize = expected.length();
- if (fileSize > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("File is larger than a StringWriter can hold");
- }
- int size = (int) fileSize;
- BufferedReader r = new BufferedReader(new FileReader(expected), size);
- char[] chars = new char[size];
- int readChars = r.read(chars);
- if (readChars != size) {
- throw new RuntimeException("Failed to read all chars of the expected result html file");
- }
- final String expectedText = new String(chars);
At this point, the variable expectedText contains what we should receive back from our request. Making the request is straightforward. First, we create a JAX-RS client:
Client client = new Client();
These clients are 'heavyweight' objects. They are relatively expensive to create and use significant resources. As such, in a production client we would create a Client
once and use it many times. For the sake of independent unit tests however, we will go ahead and create a Client
object for each test.
Next, we specify the resource we want to retrieve using a WebResource
object:
WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.html");
Then we ask the client to retrieve the resource for us, specifying that we want a text/htmlrepresentation (MediaType.TEXT_HTML_TYPE
) and specifying that we want to get back a ClientResponse
object:
ClientResponse response =
f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);
This code uses the builder pattern, where we build up our request through a chain of method calls. In this case, the chain is only two calls long. First, we call the accept
method to specify the media types we will accept in the response (text/html), then we call the get
method to actually retrieve the resource. It is possible to chain together more calls to specify other characteristics of either the request or the expected response (for more information, see the white paper on the Jersey client API).
Now that we have a representation of the resource in the form of an HTTP response, we can retrieve the HTML entity contained within the response as a string:
String returnedHTML = response.getEntity(String.class);
And finally, since this is a unit test, we check to assure what we got back matches what we expected:
assertEquals("Expected and actual HTML don't match",
expectedText, returnedHTML);
Here is the entire method as a single set of code:
- @org.junit.Test
- public void testF22Html() {
- try {
- File expected = new File("f22.html");
- long fileSize = expected.length();
- if (fileSize > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("File is larger than a StringWriter can hold");
- }
- int size = (int) fileSize;
- BufferedReader r = new BufferedReader(new FileReader(expected), size);
- char[] chars = new char[size];
- int readChars = r.read(chars);
- if (readChars != size) {
- throw new RuntimeException(
- "Failed to read all chars of the expected result html file");
- }
- final String expectedText = new String(chars);
-
- Client client = new Client();
- WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.html");
- ClientResponse response =
- f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);
- String returnedHTML = response.getEntity(String.class);
- assertEquals("Expected and actual HTML don't match", expectedText, returnedHTML);
- }
- catch (FileNotFoundException e) {
- AssertionError ae = new AssertionError("File containing expected HTML not found!");
- ae.initCause(e);
- throw ae;
- }
- catch (IOException e) {
- AssertionError ae = new AssertionError("Problems reading expected text!");
- ae.initCause(e);
- throw ae;
- }
- }
The ClientResponse
object is useful if you want to look at other characteristics of the response, such as the returned headers. In this case, we could just as well have asked for the string from the WebResource
directly. To do so, we would replace these two lines:
- ClientResponse response =
- f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);
- String returnedHTML = response.getEntity(String.class);
With this one:
- String returnedHTML =
- f22.accept(MediaType.TEXT_HTML_TYPE).get(String.class);
Testing for retrieval of the JPEG representation of an image is almost identical. The only differences are that we read in the image file as an array of bytes, ask for the response entity as an array of bytes, and compare the two as arrays of bytes. Here is the entire test method:
- @org.junit.Test
- public void testF22JPEG() {
- try {
- // Read in our expected result
- File imageFile = new File("f22.jpg");
- long fileSize = imageFile.length();
- if (fileSize > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("File is larger than a byte array can hold");
- }
- int size = (int) fileSize;
- byte[] expectedBytes = new byte[size];
- BufferedInputStream bis =
- new BufferedInputStream(new FileInputStream(imageFile), size);
- int bytesRead = bis.read(expectedBytes);
- assertEquals(size, bytesRead);
-
- // Request the representation
- Client client = new Client();
- WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.jpg");
- ClientResponse response =
- f22.accept(MediaType.WILDCARD).get(ClientResponse.class);
- // Just for fun, print out all the response headers
- MultivaluedMap<String,String> headers = response.getHeaders();
- for ( String key : headers.keySet() ) {
- System.out.println( key + "=" + headers.get(key) );
- }
- byte[] returnedBytes = new byte[0];
- returnedBytes = response.getEntity(returnedBytes.getClass());
-
- // Compare the two sets of bytes to make sure they match
- assertArrayEquals(expectedBytes,returnedBytes);
- }
- catch (FileNotFoundException e) {
- AssertionError ae =
- new AssertionError("File containing expected HTML not found!");
- ae.initCause(e);
- throw ae;
- }
- catch (IOException e) {
- AssertionError ae = new AssertionError("Problems reading expected text!");
- ae.initCause(e);
- throw ae;
- }
- }
Summary
We've barely scratched the surface of Jersey. There are many other topics we could explore, such as how to implement the creation, updating, and deletion of resources in addition to retrieving them. JAX-RS has support for all of these, and makes it easy to implement them using additional annotations. We could also explore how best to map a given object model (or database schema) into a set of resources served up by a JAX-RS web service. JAX-RS also allows for extension by it's users. For example, you can write your own types of MessageBodyReader
and MessageBodyWriter
to produce and consume custom representations for resources. It's also straightforward for a RESTful web service to be a client of other RESTful web services by mixing the Jersey core with the client library.
If you are interested in learning more about RESTful web services, JAX-RS and Jersey are a great way to learn, and provide a nice transition to building production quality services and clients.
References
- [1] The sample code
jnbAug2009-samples.jar - [2] The description of REST on Wikipedia
http://en.wikipedia.org/wiki/Representational_State_Transfer - [3] The JAX-RS specification (JSR-311)
http://jcp.org/en/jsr/detail?id=311 - [4] The Jersey site
https://jersey.dev.java.net - [5] The Jersey "Getting started" page
https://jersey.dev.java.net/use/getting-started.html - [6] The Jersey Client API White Paper
https://www.sun.com/offers/details/Java_Jersey_Client.xml - [7] The Jersey wiki
http://wikis.sun.com/display/Jersey/Main - [8] The Grizzly web services and servlet container
https://grizzly.dev.java.net - [9] Brian Gilstrap's ongoing posts on JAX-RS and Jersey
http://viewfromthefringe.blogspot.com/2009/06/jax-rs-and-jersey-posts.html