Lets start with the root XML element. I chose to call mine GetBooksResponse, and use it as a container for a collection of Book objects and a Student object. You don’t need to follow this convention.
GetBooksResponse.java
package com.dineshonjava.ws.rest; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import com.dineshonjava.ws.xml.Book; import com.dineshonjava.ws.xml.Student; /** * @author Dinesh Rajput * */ @XmlRootElement public class GetBooksResponse { private Student student; private List<Book> books = new ArrayList<Book>(); @XmlElement public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } @XmlElement @XmlElementWrapper(name = "books") public List<Book> getBooks() { return books; } public void setBooks(List<Book> books) { this.books = books; } }
JAXB’s default naming convention is to use your class or bean getter name as-is, but starting with a lower case letter. In this example, once marshaled to XML the element names will be getBooksResponse, student, and books. I will show an example of how to override the default naming later in this article.
Also notice that I placed the @XmlElement annotations on the getter methods instead of on the private fields. When placed on the private fields, JAXB will give you an error unless you add @XmlAccessorType(XmlAccessType.FIELD) at the class level.
Finally, notice the @XmlElementWrapper annotation on the List collection. This makes JAXB wrap all of the order XML elements inside of an orders XML element. This annotation can be used with an array instead of a List too.
Student.java
package com.dineshonjava.ws.xml; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; /** * @author Dinesh Rajput * */ @XmlType(name="students") public class Student { private long rollNumber; private String name; private String course; @XmlElement(name="rollNumber") public long getRollNumber() { return rollNumber; } public void setRollNumber(long rollNumber) { this.rollNumber = rollNumber; } @XmlElement(name="name") public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlElement(name="course") public String getCourse() { return course; } public void setCourse(String course) { this.course = course; } }
In the example above, we use @XmlType at the class level instead of @XmlRootElement because it is not the root element. Also notice the name parameter in each of the annotations. This is how you override JAXB’s default element naming.
Book.java
package com.dineshonjava.ws.xml; import java.util.Date; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlType; /** * @author Dinesh Rajput * */ @XmlType(propOrder = { "publishDate", "publishNumber", "publishedBooks", "bookName" } ) @XmlAccessorType(XmlAccessType.FIELD) public class Book { @XmlElement public Date publishDate; @XmlElement public long publishNumber; @XmlElement public String bookName; @XmlElement @XmlElementWrapper(name = "publishedBooks") public PublishedBooks[] publishedBooks; public Date getPublishDate() { return publishDate; } public void setPublishDate(Date publishDate) { this.publishDate = publishDate; } public long getPublishNumber() { return publishNumber; } public void setPublishNumber(long publishNumber) { this.publishNumber = publishNumber; } public String getBookName() { return bookName; } public PublishedBooks[] getPublishedBooks() { return publishedBooks; } public void setPublishedBooks(PublishedBooks[] publishedBooks) { this.publishedBooks = publishedBooks; } public void setBookName(String bookName) { this.bookName = bookName; } }
In the example above I used the @XmlAccessorType(XmlAccessType.FIELD) to allow me to place the @XmlElement annotations on the private fields instead of on the getter methods.
Also notice the propOrder parameter of the @XmlType annotation. By default JAXB will book the elements alphabetically. Use the propOrder parameter to specify the order when marshaling to XML. The values are the bean names, not the overridden names in the @XmlElement(name = “overridenName”) annotation.
Finally, notice the @XmlElementWrapper used on a PublishedBooks[] array. It works with arrays and Lists.
PublishedBooks.java
package com.dineshonjava.ws.xml; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; /** * @author Dinesh Rajput * */ @XmlType(propOrder = { "bookId", "description", "quantity", "unitPrice", "subTotal", "tax", "total" } ) public class PublishedBooks { private long bookId; private String description; private short quantity; private double unitPrice; @XmlElement public long getBookId() { return bookId; } public void setBookId(long bookId) { this.bookId = bookId; } @XmlElement public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @XmlElement public short getQuantity() { return quantity; } public void setQuantity(short quantity) { this.quantity = quantity; } @XmlElement public double getUnitPrice() { return unitPrice; } public void setUnitPrice(double unitPrice) { this.unitPrice = unitPrice; } @XmlElement public double getSubTotal() { return unitPrice * quantity; } @XmlElement public double getTax() { return getSubTotal() * 0.15F; } @XmlElement public double getTotal() { return getSubTotal() + getTax(); } }
In the example above there are no setter methods that correspond to getSubTotal, getTax and getTotal. Now lets create a JAX-RS RESTful web service that can return this object graph in the response.
BookResource.java
package com.dineshonjava.ws.order; import java.util.Date; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import com.dineshonjava.ws.error.BookNotFoundException; import com.dineshonjava.ws.rest.GetBooksResponse; import com.dineshonjava.ws.xml.Book; import com.dineshonjava.ws.xml.PublishedBooks; import com.dineshonjava.ws.xml.Student; /** * @author Dinesh Rajput * */ @Path("/order/books") public class BookResource { @GET @Produces("text/xml") public GetBooksResponse getBooks() { GetBooksResponse response = new GetBooksResponse(); Student student = new Student(); PublishedBooks publishedBooks1; PublishedBooks publishedBooks2; // student student.setRollNumber(12345); student.setName("Dinesh Rajput"); student.setCourse("Computer Science"); response.setStudent(student); // first book Book book1 = new Book(); book1.setPublishNumber(54321); book1.setPublishDate(new Date()); book1.setBookName("Java"); publishedBooks1 = new PublishedBooks(); publishedBooks1.setBookId(77777); publishedBooks1.setDescription("winning lottery ticket"); publishedBooks1.setQuantity((short) 10); publishedBooks1.setUnitPrice(5.00F); publishedBooks2 = new PublishedBooks(); publishedBooks2.setBookId(12121212); publishedBooks2.setDescription("Real World Java EE Patterns Rethinking Best Practices"); publishedBooks2.setQuantity((short) 1); publishedBooks2.setUnitPrice(40.40F); book1.setPublishedBooks(new PublishedBooks[] { publishedBooks1, publishedBooks2 } ); response.getBooks().add(book1); // second book Book book2 = new Book(); book2.setPublishNumber(12345); book2.setPublishDate(new Date()); publishedBooks1 = new PublishedBooks(); publishedBooks1.setBookId(787878); publishedBooks1.setDescription("JavaServer Faces 2.0, The Complete Reference"); publishedBooks1.setQuantity((short) 10); publishedBooks1.setUnitPrice(31.49F); publishedBooks2 = new PublishedBooks(); publishedBooks2.setBookId(1111111); publishedBooks2.setDescription("Beginning Java EE 6 with GlassFish 3, Second Edition"); publishedBooks2.setQuantity((short) 1); publishedBooks2.setUnitPrice(41.73F); book2.setPublishedBooks(new PublishedBooks[] { publishedBooks1, publishedBooks1 } ); response.getBooks().add(book2); return response; } @GET @Path("{publishNumber}") @Produces(MediaType.TEXT_PLAIN) public Response updateOrder(@PathParam("publishNumber") String publishNumber) throws BookNotFoundException { ResponseBuilder response; if ("12345".equals(publishNumber)) { response = Response.status(Response.Status.ACCEPTED).entity( "Saved changes to book '" +publishNumber+ "'."); } else { throw new BookNotFoundException("Publisher number '" + publishNumber + "' does not exist."); } return response.build(); } }
Notice the @Produces(“text/xml”) annotation, and that the method returns a GetBooksResponse object. Since the GetBooksResponse is annotated with JAXB annotations, JAX-RS will automatically marshal the response to XML.
Next lets add a method that takes part of the object graph as a request parameter. We’ll start by creating an object to represent the XML root element:
UpdateBookRequest.java
package com.dineshonjava.ws.order; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import com.dineshonjava.ws.xml.Book; /** * @author Dinesh Rajput * */ @XmlRootElement public class UpdateBookRequest { private Book book; @XmlElement public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } }
Now lets use this object in a PUT request:
@PUT @Path("{publishNumber}.xml") @Produces(MediaType.TEXT_PLAIN) public Response updateOrder(@PathParam("publishNumber") String publishNumber) throws BookNotFoundException { ResponseBuilder response; if ("12345".equals(publishNumber)) { response = Response.status(Response.Status.ACCEPTED).entity( "Saved changes to book '" +publishNumber+ "'."); } else { throw new BookNotFoundException("Publisher number '" + publishNumber + "' does not exist."); } return response.build(); }
Since the UpdateBookRequest is annotated with JAXB annotations, JAX-RS will automatically unmarshal it from XML.
This example returns a javax.ws.rs.core.Response built using javax.ws.rs.core.ResponseBuilder. You can use ResponseBuilder to set response headers, the status code, and many other things. The response body is called the entity, and you can place anything in it. For example, a String, or a JAXB annotated object graph.
This example also throws an BookNotFoundException:
BookNotFoundException.java
package com.dineshonjava.ws.error; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; /** * @author Dinesh Rajput * */ public class BookNotFoundException extends WebApplicationException { /** * version */ private static final long serialVersionUID = 1L; public BookNotFoundException(String message) { super(Response.status(Status.NOT_FOUND).entity(message).type( MediaType.TEXT_PLAIN).build()); } }
The custom exception extends the WebApplicationException in JAX-RS. There are many constructors in WebApplicationException. I chose to use the one that lets me provide the complete response data.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>JaxRSJaxBApps</display-name> <servlet> <servlet-name>jersey-serlvet</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jersey-serlvet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Directory structure for this web application.
Now deploy this application to the tomcat server and hit the following urls.
http://localhost:8181/doj/order/books
http://localhost:8181/doj/order/books/12345
Download SourceCode and Libs
JaxRSJaxBApps.zip
References
1. JAVA REST Web Services
2. Wikipedia for REST Web Service