Serializing a POJO to xml or json using JAXB
Background
I originally wrote this series back in 2013 on a wiki. At that point in my career, I had spent several years working on java based projects, specifically webservices with jax-rs and Jersey (Sun/Oracle’s reference implementation of jax-rs). I became pretty proficient, and decided to write down some of my knowledge in a form “how-to” and starterkit / template project. I got good feedback that it was helpful both from teammates and external random techies.
I am 100% sure this info is outdated.
Tutorial List
- Create a “Hello World” jersey project
- Serializing a POJO to json using built in jersey support
- Serializing a POJO to xml or json using JAXB
Starter Kit
I had created a starter kit / template project.
To clone the repository locally:
git clone git@github.com:jasonray/jersey-starterkit.git
Have at it!
Purpose of this tutorial
On most jax-rs web services, you are going to have the need to take java objects and serialize to xml or json. In the Serializing a POJO to json using default built in jersey support, I demonstrated how to serialize to json using built-in support and with no annotations. In this tutorial, I explore the use of annotations to serialize to json or xml.
This tutorial assumes that you already have a jersey project. If not, follow the see the Create a “Hello World” jersey project tutorial.
JAXB
JSR-222, or JAXB, is a standard for binding a java class to XML. Sun/Oracle has created a JAXB reference implementation that is included with Jersey.
JAXB allows for you to annotate your java classes to describe the relationship between your classes and xml.
For the purpose of usage with this tutorial, the following are the key annotations:
- @XmlRootElement: place at the class level to instruct that this is a root level class that can be serialized. You can specify the root xml element name. This annotation will be placed on all root classes that will be returned from your rest web services.
- @XmlElement: place at the field/method level to instruct that this is an item that should be serialized, and placed as a xml element. For your purposes this can be placed on the field or the getter/setter methods of the classes returned by your rest web services.
- @XmlAttribute: very similar to the @XmlElement, except that the item is serialized as an xml attribute. Complex objects cannot be serialized as an xml attribute.
Built in POJO mapping
If you worked through the example on Serializing a POJO to json using default built in jersey support, remember that we invoked the web service and asked for json:
> curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"id":"1","name":"Mighty Pulpo","city":"austin","state":"TX"}
Suppose that we asked for XML:
> curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
(error)
Looking in the web server logs reveals that Jersey could not determine how to map the customer object to xml:
javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class jayray.net.Orders.Customer, and Java type class jayray.net.Orders.Customer, and MIME media type application/xml was not found
The problem is that the built in json support does not handle xml.
Time for JAXB.
Utilizing JAXB for Object Serialization
Create a simple java DTO. To this class, add the @XmlRootElement annotation:
@XmlRootElement()
public class Customer {
private String id;
private String name;
private String city;
private String state;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Create a resource class, specifying that it produces JSON
and XML
to expose this:
@Path("customer")
public class CustomerResource {
@GET
@Path("id/{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Customer getCustomer(@PathParam("id") String id) {
Customer customer = new Customer();
customer.setId(id);
customer.setCity("austin");
customer.setState("TX");
customer.setName("Mighty Pulpo");
return customer;
}
}
Compile and deploy and exercise:
> curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><customer><city>austin</city><id>1</id><name>Mighty Pulpo</name><state>TX</state></customer>
> curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"id":"1","name":"Mighty Pulpo","city":"austin","state":"TX"}
Looks good! Note that we requested the response by JSON in the first request and XML in the second request. Support for elements seem to work out pretty well.
Support for nested objects
JAXB (as does the built in pojo mapping) also supports hierarchal object structures. Suppose that we want the address to be represented as a child object. Refactoring leads to:
public class Address {
private String city;
private String state;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
@XmlRootElement()
public class Customer {
private String id;
private String name;
private Address address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Note that because the address is not a root element, it does not need the @XmlRootElement tag on it.
Compile and deploy and exercise (formatted for readability):
> curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<address>
<city>austin</city>
<state>TX</state>
</address>
<id>1</id>
<name>Mighty Pulpo</name>
</customer>
> curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"id":"1","name":"Mighty Pulpo","city":"austin","state":"TX"}
Looks good! Support for nested objects looks to work out pretty well, also.
Support for arrays/collections
Jersey/JAXB supports arrays/collections. Let’s turn the address into a collection of addresses:
@XmlRootElement()
public class Customer {
private String id;
private String name;
private List<Address> addresses = new ArrayList<Address>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Address> getAddresses() {
return addresses;
}
public void setAddresses(List<Address> address) {
this.addresses = address;
}
}
Make supporting changes in the resource class, compile, deploy, and exercise:
> curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<addresses>
<city>austin</city>
<state>TX</state>
</addresses>
<addresses>
<city>sterling</city>
<state>VA</state>
</addresses>
<id>1clear</id>
<name>Mighty Pulpo</name>
</customer>
> curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"addresses":[{"city":"austin","state":"TX"},{"city":"sterling","state":"VA"}],"id":"1","name":"Mighty Pulpo"}
Hmm. The xml doesn’t look quite right. The naming is not very natural. Rather, We would desire:
<customer>
<addresses>
<address>
</address>
<address>
</address>
</addresses>
</customer>
OR
<customer>
<address>
</address>
<address>
</address>
</customer>
If the latter is desired, we simply need to override the element name with @XmlElement(name="address")
:
@XmlRootElement()
public class Customer {
private String id;
private String name;
private List<Address> addresses = new ArrayList<Address>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name="address")
public List<Address> getAddresses() {
return addresses;
}
public void setAddresses(List<Address> address) {
this.addresses = address;
}
}
Support for xml attributes
Jersey/JAXB also supports xml attributes. Suppose that we want to address an attribute to address that denotes if the address is a home or work address.
In the class below, I use @XmlAccessorType(XmlAccessType.NONE)
with @XmlAttribute
to specify which fields are serialized via element vs attribute.
@XmlAccessorType(XmlAccessType.NONE)
public class Address {
@XmlElement
private String city;
@XmlElement
private String state;
@XmlAttribute
private String addressType;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getAddressType() {
return addressType;
}
public void setAddressType(String addressType) {
this.addressType = addressType;
}
}
Compile, deploy, and exercise:
> curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<address addressType="home">
<city>austin</city>
<state>TX</state>
</address>
<address addressType="work">
<city>sterling</city>
<state>VA</state>
</address>
<id>1</id>
<name>Mighty Pulpo</name>
</customer>
> curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"address":[{"@addressType":"home","city":"austin","state":"TX"},{"@addressType":"work","city":"sterling","state":"VA"}],"id":"1","name":"Mighty Pulpo"}
The xml looks good, but what about the json? What are those “@” symbols doing in there?!?
The problem is in how the json parser is using the jaxb annotations. Presumably because of potential conflicts between an xml element and xml attribute with the same name, or to optimize parsing from json to object, the “@” is put into the xml attributes items in the json.
Its ugly and makes it difficult for client side parsing from javascript - you cannot use the built in javascript parsing to object.
Specifying Configuration for JSON
At times, you are going to need to specify configuration options for serializing JSON. Jersey/JAXB gives you ability to specify several configuration items, including choosing from several presets.
To specify configuration, we need to create a custom ContextResolver. This class will return a custom JSONJAXBContext, which includes a custom JSONConfiguration.
Here is an example:
@Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = { Address.class, Customer.class, CustomerResource.class };
public JAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
JSONConfiguration.natural() represents one of several presets. Below are some others and their sample from the javadocs:
<table>
<tr><td><a href='http://jersey.java.net/nonav/apidocs/1.17/jersey/com/sun/jersey/api/json/JSONConfiguration.Notation.html#NATURAL'>natural</a></td><td>{"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":[{"userid":1621,"name":"Grotefend"}]}</td></tr>
<tr><td><a href='http://jersey.java.net/nonav/apidocs/1.17/jersey/com/sun/jersey/api/json/JSONConfiguration.Notation.html#BADGERFISH'>badgerfish</a></td><td>{"userTable":{"columns":[{"id":{"$":"userid"},"label":{"$":"UserID"}},{"id":{"$":"name"},"label":{"$":"User Name"}}],"rows":{"userid":{"$":"1621"},"name":{"$":"Grotefend"}}}</td></tr>
<tr><td><a href='http://jersey.java.net/nonav/apidocs/1.17/jersey/com/sun/jersey/api/json/JSONConfiguration.Notation.html#MAPPED'>mapped</a><br>default</td><td>{"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":{"userid":"1621","name":"Grotefend"}}</td></tr>
<tr><td><a href='http://jersey.java.net/nonav/apidocs/1.17/jersey/com/sun/jersey/api/json/JSONConfiguration.Notation.html#MAPPED_JETTISON'>mapped jettison</a></td><td>{"userTable":{"columns":[{"id":"userid","label":"UserID"},{"id":"name","label":"User Name"}],"rows":{"userid":1621,"name":"Grotefend"}}}</td></tr>
</table>
Compile, deploy, and exercise:
curl -H Accept:application/xml http://localhost:8080/jersey-starterkit/rest/customer/id/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><customer><address addressType="home"><city>austin</city><state>TX</state></address><address addressType="work"><city>sterling</city><state>VA</state></address><id>1</id><name>Mighty Pulpo</name></customer>j
curl -H Accept:application/json http://localhost:8080/jersey-starterkit/rest/customer/id/1
{"address":[{"addressType":"home","city":"austin","state":"TX"},{"addressType":"work","city":"sterling","state":"VA"}],"id":"1","name":"Mighty Pulpo"}