REST web services with JAXB, JAX-RS and Sun Jersey

At the Devoxx conference in December, there were a number of interesting presentations on REST web services and one of the most interesting ones was by Paul Sandoz, one of the lead developers on the Sun Jersey JAX-RS reference implementation. You can find the slides and demos used in the presentation here.

Given the interest around developing REST web services with Java, I thought I’d create my own simple project which uses Maven, Jetty and JAX-RS to publish a simple web service. I used cURL to test it. I’ll go through the code in the project in this post. You can download the finished project from here.

Step 1: The Maven pom.xml:

I’ve not included the POM here because Maven is very verbose and the POM takes up a lot of space. Lets review some of the important elements of this configuration. The repositories and pluginrepositories elements configure Maven to look in the java.net repositories for the Jersey and JAXB libraries/plugins. Jetty is configured to run on port 9080 (I always do this since there’s often a lot of competition for port 8080 on my machine!) and we configure the JAXB plugin.

Step 2: XSD and JAXB

Although web services can use various types of input/output formats for request and response, in this example we will use XML. When using XML, it’s always a good idea to first start with an XML Schema Definition (XSD). You can then use the JAXB Maven plugin to generate Java source files from this. Below is my XSD for this project (in /src/main/resources/cars.xsd):

<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://www.theserverlabs.com/namespaces/cars"
           xmlns:c="http://www.theserverlabs.com/namespaces/cars"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified"
          xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
          jaxb:version="1.0"
          xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
          jaxb:extensionBindingPrefixes="xjc">
 
  <xs:annotation>
    <xs:appinfo>
      <jaxb:globalBindings>
        <xjc:simple />
      </jaxb:globalBindings>
    </xs:appinfo>
  </xs:annotation>
 
 
<xs:element name="Cars">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="car" minOccurs="0" maxOccurs="unbounded" type="c:CarType"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>
 
<xs:element name="Car" type="c:CarType" />
 
<xs:complexType name="CarType">
    <xs:attribute name="id" type="xs:int" />
    <xs:attribute name="make" type="xs:string" />
    <xs:attribute name="model" type="xs:string" />
    <xs:attribute name="price" type="xs:int" />
</xs:complexType>
 
</xs:schema>

This is a really simple schema that contains two elements - car and list of cars. The car has an id, make, model and a price. A CarList contains a list of Car objects. Simple really. To generate the Java classes that correspond to these XML objects, we use the Maven JAXB plugin which is configured in the pom.xml file (See step 1). The JAXB plugin runs in the Maven generate-sources phase so we run it like this:

mvn generate-sources

Step 3: Configure the Jersey Servlet in the web.xml

As with any JEE web framework, we have to register Jersey with the Servlet container in the web.xml. Below is the entry in my web.xml which send all requests to /rest in the application to the Jersey Servlet. It also tells Jersey where to look for annotated JAX-RS Java classes via the com.sun.jersey.config.property.packages parameter.

    <servlet>
        <servlet-name>jersey</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>com.theserverlabs.test.rest</param-value>
        </init-param>
    </servlet>
 
    <servlet-mapping>
    	<servlet-name>jersey</servlet-name>
    	<url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

Step 4: Creating the Resource files

In JAX-RS you allow people access to your services by publishing Resources. Resources are just simple java classes with some additional JAX-RS annotations. These annotations express the path of the resource (the URL that you use to access it), the HTTP method that you use to call a certain method (e.g GET, POST) and the MIME type that a method accepts/responds with.

A GET method

A snippet of code from the CarsResource class is shown below:

@Path("cars")
public class CarsResource {
 
    @GET
    @Produces({"application/xml","application/json"})
    public Cars getAll() {
        Cars cars = new Cars();
        cars.getCars().addAll(CarDb.getInstance().getCars());
    	return cars;
    }
    ...
}

This shows the use of the @Path annotation to declare that you access this resource via the URL /cars (meaning the total URL is /rest/cars since we configured the web.xml to divert all requests to /rest to Jersey). The getAll() method is associated with HTTP GET request to the URL /cars i.e. when a client issues a HTTP GET to /cars, Jersey will call getAll(). Note that the @Produces annotation allows us to return either XML or JSON. Note also that we can return a JAXB object (Cars in this case) and Jersey will do handle the XML or JSON conversion for us.

To see this in action, we deploy the application:

mvn jetty:run

Once the app is up and running, we can use cURL to issue a few test requests to see that things are working:

curl -v -X GET -H"Accept: application/xml" http://localhost:9080/rest/cars

gets a list of the cars in XML format. The output should be something like this:

< ?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cars xmlns="http://www.theserverlabs.com/namespaces/cars">
  <car model="Clio" make="Renault" id="1"/>
  <car model="306" make="Peugeot" id="0"/>
</cars>

Doing the same but requesting in JSON format:

curl -v -X GET -H"Accept: application/json" http://localhost:9080/rest/cars

yields the following result:

{"car":[
   {"@model":"Clio","@make":"Renault","@id":"1"},
   {"@model":"306","@make":"Peugeot","@id":"0"}
]}

A POST (create) method

Following REST semantics, you use a HTTP POST method to create a new resource. This is because a POST method is the only method that doesn’t have to be idempotent (i.e. does exactly the same thing every time). Since creation of a resource normally requires generating a new unique ID etc, creation often cannot be idempotent. The POST method on CarsResource.java is shown below:

    @POST
    @Consumes({"application/xml","application/json"})
    public Response createNewCar(JAXBElement<car> car) {
        CarDb.getInstance().addCar(car.getValue());
        URI carUri = uriInfo.getAbsolutePathBuilder().
                path(car.getValue().getId().toString()).
                build();
        return Response.created(carUri).build();
    }

The method is annotated with @POST meaning that any HTTP POST requests to the /cars URI will cause Jersey to call this method. The body of the POST request is expected to be a JAXB element that can be converted to a Car object.

NOTE: the JAXBElement part is very important - it allows a non-root XML Element (i.e. an element that is not normally allowed to be the 1st element in a valid XML document according to the XSD) to be supplied.

In the method body we generate a HTTP 204 method response indicating that the object has been created and what it’s URL is. Below is a create command passing XML as arguments and the response from the server:

curl -v -X POST --data-binary "<car make=\"blah\"/>" -H"Content-Type: application/xml" -H"Accept: application/xml" http://localhost:9080/rest/cars/
< HTTP/1.1 201 Created
< Location: http://localhost:9080/rest/cars/3
< Content-Length: 0
< Server: Jetty(6.1.10)

Requesting a single car

In the CarsResource class, there is a getCar() method which handles requests that deal with a single Car resource. These requests have URLs of the form /cars/[id of the car] e.g. you would make a HTTP GET request to /cars/1 to get some XML that described the Car with ID 1. We delegate the processing of these requests to the CarResource class via the getCar() method:

    @Path("{carid}/")
    public CarResource getCar(@PathParam("carid") String carid) {
        return new CarResource(carid);
    }

In this instance, the @Path annotation indicates the sub-path within the path already specified at class level. The use of the {carid} syntax binds the URL element to the method parameter. The newly constructed CarResource object takes care of the response.

Below is the code for the GET method in CarResource:

public class CarResource {
 
    @Context private UriInfo uriInfo;
    @Context private Request request;
 
    private String id; 
    private Car car;
 
    public CarResource(String id) {
        this.id = id;
        car = CarDb.getInstance().findById(id);
    }
 
    @GET
    @Produces({"application/xml","application/json"})
    public Car getCar() {
    	return car;
    }

When we construct a CarResource instance, we look up the details of the car in the DB (the CarDb class is a mock database). in the GET method, we just need to return the Car. Below is an example request/response using cURL:

curl -H "accept: application/xml" localhost:9080/rest/cars/1
< ?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<car xmlns="http://www.theserverlabs.com/namespaces/cars" model="Clio" make="Renault" id="1"/>

Updating a resource

To update a resource using REST semantics, you do a HTTP PUT which replaces entirely the original content. The CarResource class contains a PUT method that demonstrates this:

    @PUT
    @Consumes({"application/xml","application/json"})
    public JAXBElement<car> putCar(JAXBElement</car><car> car) {
        CarDb.getInstance().updateCar(id, car.getValue());
        return car;
    }

Note that in JAX-RS with JAXB, the HTTP PUT method is basically identical to the HTTP POST we saw earlier for creating a resource. Below is an example of it in action:

curl -v -X PUT --data-binary "<car id=\"2\" make=\"honda\" model=\"civic\"/>" -H"Content-Type: application/xml" -H"Accept: application/xml" http://localhost:9080/rest/cars/3
< HTTP/1.1 200 OK
< Content-Type: application/xml
< Transfer-Encoding: chunked
< Server: Jetty(6.1.10)

Deleting a resource

Finally, to complete the CRUD operations, we have the HTTP DELETE method. It does exactly what you expect it to do - you issue a HTTP DELETE against a URL and it removes the content associated with that URL. The JAX-RS code in the CarResource method looks like this:

    @DELETE
    public void deleteCar() {
        CarDb.getInstance().deleteCar(id);
    }

and below is an example of it working:

curl -v -X DELETE http://localhost:9080/rest/cars/3
< HTTP/1.1 204 No Content
< Server: Jetty(6.1.10)
<

I hope that this was an interesting introduction to JAX-RS with JAXB and Jersey and that it inspires you to look into REST, JAX-RS and Jersey in particular.

9 Comments on “REST web services with JAXB, JAX-RS and Sun Jersey”

  1. #1 Jacobo
    on Jan 23rd, 2009 at 12:54 pm

    For those interested in Restful design patterns and anti-patterns, Stefan Tilkov from InnoQ gave a nice presentation in Devoxx 08 on this subject.
    Have a look if you got the chance: http://www.javoxx.com/download/attachments/1705921/D8_C_10_04_05.pdf.

  2. #2 Andrew
    on Feb 2nd, 2009 at 3:04 pm

    Would you happen to have any examples focused on processing form submissions including file uploads?

  3. #3 Kevin McCormack
    on Feb 2nd, 2009 at 3:34 pm

    @Andrew, I don’t have any concrete examples creating a REST API for file upload but I’ve included a link below to a discussion thread on doing file upload with Jersey.

    Hope this helps.

    http://markmail.org/message/dvl6qrzdqstrdtfk#query:sun%20jersey%20file%20upload+page:1+mid:d24a2jmzea2wwe62+state:results

  4. #4 mark
    on Apr 20th, 2009 at 3:43 pm

    Very helpful example. I still refer to it and refer friends to it that want a simple example of POST/PUT processing in Jersey.

    Is there a way, say, in the method that handles POST to tell Jersey or JAXB that the namespace in the input “car” JAXBElement is optional? Or to inform the handling method to supply the namespace if the input XML omits it?

  5. #5 Ramesh
    on Sep 17th, 2009 at 6:47 pm

    Thank you. That was a very helpful example. I tried the same in my project and everything worked.

    In the JSON output, is there a way to remove the @ in front of all the attributes. I did some search and was not able to find an answer. Please let me know if you are aware of way to get rid of that.

    Thanks,
    Ramesh

  6. #6 Raji
    on Jun 18th, 2010 at 3:41 pm

    Thanq. I have tried same thing but in my project its not working. Its giving “503 currently service is not available” error i am getting.Can you please help on this…

    Thanks,
    Raji

  7. #7 Raul
    on Aug 3rd, 2010 at 9:45 pm

    Can I post JSON? I am getting only post with XML objects with complex types

  8. #8 AH
    on Aug 8th, 2010 at 2:27 am

    Honestly, this is the only useful example I found in the web covering JAXB and Jersey.

    I modified NetBeans hello world for this implementation

    I appreciate your efforts

  9. #9 Kevin McCormack
    on Aug 9th, 2010 at 7:33 am

    @Raul, you should be able to post JSON. In the “A POST (create) method” section, I use an XML example. Changing various aspects of the curl petition should allow you to post JSON e.g. somethingl like the following should work (note that I haven’t been able to test this):

    curl -v -X POST –data-binary “{”car”:[ {"@make":"blah"} ]}” -H”Content-Type: application/json” -H”Accept: application/json” http://localhost:9080/rest/cars/

Leave a Comment