JAXB - Java working with XML overview (tutorial)

Complete Source Code for all examples below

If you have read the previous tutorials about the JAXP (DOM/SAX), you would see that the approach is quite simple and but is very coersive to data. JAXB provides a different approach by looking at the Java object and corresponding XML in terms of binding. JAXB site has quite a lot of material but to give you a jump start please read on.

In most recent version of JAXB the binding are specified by annotation the POJO classes.

 

To start we will consider the following example:

We have a Person class that allows us to hold the person name and addresses, we also have the Address class to store the address information. The task is to convert the Person object(s) with corresponding address(es) into XML and back. We will take this in two steps: 1st - do the above task for a single person, 2nd - do the above task for collection of people.

 

Solution for single person + addresses:

The Entities:

 

package dp.test.xml.jaxb.entity;

import java.util.Collection;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "person")
// NONE forces JAXB to look at annotations on fields and properties, alternatively 
// we could use FIELD or PROPERTIES and omit the individual annotations.
@XmlAccessorType(XmlAccessType.NONE)
// propOrder - defines the sequence of fields in the XML, if specified it has to specify all fields
@XmlType(name = "person", propOrder = { "name", "addresses" })
public class Person
{
	
	@XmlAttribute
	private int id;
	@XmlElement
	private String name;
	// Wrapper allows to add extra layer of tags around the collection
	@XmlElementWrapper(name = "addresses")
	@XmlElement(name = "address")
	private Collection
addresses; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Collection
getAddresses() { return addresses; } public void setAddresses(Collection
addresses) { this.addresses = addresses; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((addresses == null) ? 0 : addresses.hashCode()); result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (addresses == null) { if (other.addresses != null) return false; } else if (!addresses.equals(other.addresses)) return false; if (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } public String toString() { final String TAB = " "; String retValue = ""; retValue = "Person ( " + super.toString() + TAB + "id = " + this.id + TAB + "name = " + this.name + TAB + "addresses = " + this.addresses + TAB + " )"; return retValue; } package dp.test.xml.jaxb.entity; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement(name = "address") @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "address", propOrder = { "house", "street", "city", "postcode", "country" }) public class Address { @XmlAttribute private long id; @XmlElement private String house; @XmlElement private String street; @XmlElement private String city; @XmlElement private String postcode; @XmlElement private String country; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getHouse() { return house; } public void setHouse(String house) { this.house = house; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getPostcode() { return postcode; } public void setPostcode(String postcode) { this.postcode = postcode; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((city == null) ? 0 : city.hashCode()); result = prime * result + ((country == null) ? 0 : country.hashCode()); result = prime * result + ((house == null) ? 0 : house.hashCode()); result = prime * result + (int) (id ^ (id >>> 32)); result = prime * result + ((postcode == null) ? 0 : postcode.hashCode()); result = prime * result + ((street == null) ? 0 : street.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (city == null) { if (other.city != null) return false; } else if (!city.equals(other.city)) return false; if (country == null) { if (other.country != null) return false; } else if (!country.equals(other.country)) return false; if (house == null) { if (other.house != null) return false; } else if (!house.equals(other.house)) return false; if (id != other.id) return false; if (postcode == null) { if (other.postcode != null) return false; } else if (!postcode.equals(other.postcode)) return false; if (street == null) { if (other.street != null) return false; } else if (!street.equals(other.street)) return false; return true; } public String toString() { final String TAB = " "; String retValue = ""; retValue = "Address ( " + super.toString() + TAB + "id = " + this.id + TAB + "house = " + this.house + TAB + "street = " + this.street + TAB + "city = " + this.city + TAB + "postcode = " + this.postcode + TAB + "country = " + this.country + TAB + " )"; return retValue; } } }

The Code:

 

package dp.test.xml.jaxb;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.junit.Test;

import dp.test.xml.jaxb.entity.Address;
import dp.test.xml.jaxb.entity.People;
import dp.test.xml.jaxb.entity.Person;

/**
 * The basics of JAXB is to provide binding between java objects and the XML
 * via creating intermediate binding java classes implementations that allow to
 * extract the data.
 * 
 * @author DPavlov
 */
public class JAXBExample
{

	@Test
	public void testMarshallUnmarshalExample() throws JAXBException {
		
		// You can specify the packages separated by colon 
		// e.g. "dp.test.xml.jaxb:dp.test.xml.jaxb.entity"
		// BUT each of those packages must have the ObjectFactory class
		final JAXBContext context = JAXBContext.newInstance("dp.test.xml.jaxb.entity");
		
		final Address addr = new Address();
		addr.setId(1);
		addr.setHouse("221b");
		addr.setStreet("Baker Str");
		addr.setCity("London");
		addr.setPostcode("NW1 6XE");
		addr.setCountry("UK");
		
		final Person sherlock = new Person();
		sherlock.setId(1);
		sherlock.setName("Sherlock Holmes");
		sherlock.setAddresses(Arrays.asList(addr));
		
		final StringWriter writer = new StringWriter();
		// this is where we convert the object to XML
		context.createMarshaller().marshal(sherlock, writer);
		
		System.out.println(writer.toString());
		
		// this is where we convert the XML to object
		final Person fromXML = (Person) context.createUnmarshaller().unmarshal(
				new StringReader(writer.toString()));
		
		System.out.println(fromXML.toString());
	}
}

The output:

Object:

Person ( dp.test.xml.jaxb.entity.Person@7a2946f4    id = 1    name = Sherlock Holmes    addresses = 
[
Address ( dp.test.xml.jaxb.entity.Address@e714508e    id = 1    house = 221b    
       street = Baker Str    city = London    postcode = NW1 6XE    country = UK     )
]     )

 

XML:



Sherlock Holmes

221b Baker Str London NW1 6XE UK

 

If you run the above code you WILL get an error during runtime, due to missing ObjectFactory class. This is because we have constructed the Context using the package JAXBContext.newInstance("dp.test.xml.jaxb.entity"); declaration. In this case JAXB need to have ObjectFactory class to be present in every package supplied to context (dp.test.xml.jaxb.entity in this instance). If the context would be created using plain class names this would not have been necessary.

The ObjectFactory (to make the above run):

 

package dp.test.xml.jaxb.entity;

import javax.xml.bind.annotation.XmlRegistry;

/**
 * Basically this class must be contained in every package that is supplied as parameter to 
 * {@link javax.xml.bind.JAXBContext#newInstance(String)}. The convention is to create a
 * factory method for every class to be recognised by JAXB where name of method is
 * "create" + [class name]. i.e. the Address class has createAddress() method that has no 
 * arguments and has return type compliance.
 * 
 * @author DPavlov
 */
@XmlRegistry
public class ObjectFactory
{

	public Address createAddress() {
		return new Address();
	}
	
	public Person createPerson() {
		return new Person();
	}
	
	public People createPeople() {
		return new People();
	}
	
}


Solution for collection of people + addresses:

JAXB has an unfortunate drawback that it cannot easily nest collection objects within an XML. So that if you want to have an XML with lots of objects you need to introduce a wrapper to hold them.

 

The people class:

 

package dp.test.xml.jaxb.entity;

import java.util.Collection;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
 * In order to achieve marshalling/unmarshalling collection of object
 * to/from a single xml file we need a wrapper object such as this one
 * that would allows to wrap our individual person objects into
 * a list of people.
 * 
 * @author DPavlov
 */
@XmlRootElement(name = "people")
@XmlAccessorType(XmlAccessType.NONE)
@XmlType(name = "people")
public class People
{

	@XmlElement(name = "person")
	private Collection people;

	
	public Collection getPeople() {
		return people;
	}

	
	public void setPeople(Collection people) {
		this.people = people;
	}


	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((people == null) ? 0 : people.hashCode());
		return result;
	}


	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		People other = (People) obj;
		if (people == null) {
			if (other.people != null)
				return false;
		} else if (!people.equals(other.people))
			return false;
		return true;
	}

	public String toString()
	{
	    final String TAB = "    ";
	    
	    String retValue = "";
	    
	    retValue = "People ( "
	        + super.toString() + TAB
	        + "people = " + this.people + TAB
	        + " )";
	
	    return retValue;
	}
	
	
	
}


The code to run this:

 

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.junit.Test;

import dp.test.xml.jaxb.entity.Address;
import dp.test.xml.jaxb.entity.People;
import dp.test.xml.jaxb.entity.Person;

/**
 * The basics of JAXB is to provide binding between java objects and the XML
 * via creating intermediate binding java classes implementations that allow to
 * extract the data.
 * 
 * @author DPavlov
 */
public class JAXBExample
{

	
	@Test
	public void testMarshallUnmarshalCollectionsExample() throws JAXBException {
		
		// You can specify the packages separated by colon 
		// e.g. "dp.test.xml.jaxb:dp.test.xml.jaxb.entity"
		// BUT each of those packages must have the ObjectFactory class
		final JAXBContext context = JAXBContext.newInstance("dp.test.xml.jaxb.entity");
		
		final Address addr = new Address();
		addr.setId(1);
		addr.setHouse("221b");
		addr.setStreet("Baker Str");
		addr.setCity("London");
		addr.setPostcode("NW1 6XE");
		addr.setCountry("UK");
		
		final Person sherlock = new Person();
		sherlock.setId(1);
		sherlock.setName("Sherlock Holmes");
		sherlock.setAddresses(Arrays.asList(addr));

		final Person wattson = new Person();
		wattson.setId(2);
		wattson.setName("Dr Wattson");
		wattson.setAddresses(Arrays.asList(addr));
		
		final People people = new People();
		people.setPeople(Arrays.asList(sherlock, wattson));
		
		final StringWriter writer = new StringWriter();
		// this is where we convert the object to XML
		context.createMarshaller().marshal(people, writer);
		
		System.out.println(writer.toString());
		
		// this is where we convert the XML to object
		final People fromXML = (People) context.createUnmarshaller().unmarshal(
				new StringReader(writer.toString()));
		
		System.out.println(fromXML.toString());
		
	}
	
}

The output:

Object:

People ( dp.test.xml.jaxb.entity.People@cac1566c    people = 
[
Person ( dp.test.xml.jaxb.entity.Person@7a2946f4    id = 1    name = Sherlock Holmes    addresses = 
   [
   Address ( dp.test.xml.jaxb.entity.Address@e714508e    id = 1    
          house = 221b    street = Baker Str    city = London    postcode = NW1 6XE    country = UK     )
   ]     ), 
Person ( dp.test.xml.jaxb.entity.Person@ffc1bb00    id = 2    name = Dr Wattson    addresses = 
   [
   Address ( dp.test.xml.jaxb.entity.Address@e714508e    id = 1    
         house = 221b    street = Baker Str    city = London    postcode = NW1 6XE    country = UK     )
   ]     )
]     )

 

XML:




Sherlock Holmes

221b Baker Str London NW1 6XE UK
Dr Wattson
221b Baker Str London NW1 6XE UK

 

 

Hopefully this gives you a kick start as to how the JAXB library can be used. One other fruity feature is the on-the-fly-schema generation from the POJO annotations. There is a tool for doing that in JAXB command line, but we can do it programmatically.

 

Schema Generation Programatically from JAXB

 

package dp.test.xml.jaxb;

import java.io.IOException;
import java.io.Writer;

import javax.xml.bind.SchemaOutputResolver;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;

/**
 * This is a basic example of resolver whose job is basically to provide
 * The JAXB schema generator with the result object where the output can be stored.
 * 
 * @author DPavlov
 */
public class WriterSchemaOutputResolver extends SchemaOutputResolver
{

	private final Writer out;
	
	
	public WriterSchemaOutputResolver(Writer out) {
		super();
		this.out = out;
	}


	@Override
	public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
		final StreamResult result = new StreamResult(this.out);
		result.setSystemId("no-id"); // Result MUST contain system id, or JAXB throws an error message
		return result;
	}

}


The SchemaOutputResolver class to provide the basic capability to save the streamed result:

 

The schema generation code:

 

package dp.test.xml.jaxb;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.junit.Test;

import dp.test.xml.jaxb.entity.Address;
import dp.test.xml.jaxb.entity.People;
import dp.test.xml.jaxb.entity.Person;

/**
 * The basics of JAXB is to provide binding between java objects and the XML
 * via creating intermediate binding java classes implementations that allow to
 * extract the data.
 * 
 * @author DPavlov
 */
public class JAXBExample
{

	@Test
	public void testGenerateSchemaExample() throws JAXBException, IOException {
		
		final JAXBContext context = JAXBContext.newInstance(Person.class);
		
		final StringWriter writer = new StringWriter();
		// This call generates all tree of JAXB mappings into schema for Person class
		context.generateSchema(new WriterSchemaOutputResolver(writer));
		
		System.out.println(writer.toString());

	}
}

The schema output:




  

  

  
    
      
      
        
          
            
          
        
      
    
    
  

  
    
      
      
      
      
      
    
    
  



 

Hope all of this will come in handy!

This page was last updated on: 13/04/2012 11:09