Java and XML - Part 3 (JAXB)
Join the DZone community and get the full member experience.
Join For Free[Editor's note: this article is part 3 of a series. The first two parts can be found here and here.]
Besides the JAXP API for handling XML files, which I mentioned in my previous article[1], there is another API in Java named JAXB[2] (Java Architecture for XML Binding[3]). JAXB is also used for persisting Java objects as XML files. Persisting this way they are more human readable than if they were to persist via Java serailization[4] and easier for manipulation from other applications. For example, if you need only a few properties from the persistent object as XML file they can be easily read via JAXP or similar parsers like JDOM2.
JAXB came to Java with Java 6. Before Java6, other 3rd party libraries were used for this kind of processing. Some of them are XStream from the firm Codehouse and XMLBeans from the Apache Software Foundation. Other libraries are: JiBX, TopLink, EclipseLink MOXy, Apache Common Betwixt, CookXML, Castor XML (I didn't know that there are so many other libraries for XML data binding, nor I can explain why so many different implementations exist. It seems every firm and organization wrote their own implementation).
The two main terms bound to JAXB are marshalling and unmarshalling.
- Marshalling is the process when data from Java objects are written into XML.
- Unmarshalling is the process when XML data is transformed into Java objects.
Code Example Marshalling Handler.
JAXBContext jc = JAXBContext.newInstance(SomeClass.class); Marshaller m = jc.createMarshaller(); // output pretty printed m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); m.marshal(someClassObj, someFile); m.marshal(someClassObj, System.out);Code Example Unmarshalling Handler.
JAXBContext jc = JAXBContext.newInstance(SomeClass.class); Unmarshaller u = jc.createUnmarshaller(); unmarshallesObject = u.unmarshal(file);After persisting the objects into XML files, they can be unmarshalled from any other Java application which has the class definition of the annotated JAXB classes on their classpath, which means that if you marshalled the obect with one JVM, it can be unmarshalled with other JVM as well.
In this article, I will manually create simple classes to demonstrate the most used JAXB cases. Another approach for doing this is to use XSD as parameter via the xjc tool for creating all annotated classes. For further and detailed explaination of the tool:
- Java SE Documentation: Launching xjc [4]
- GlassFish Metro Site: Launching xjc[5]
- GlassFish Metro Site: xjc Specification 2.0[6]
There is also another tool with which you can create XSD files from the annotated classes known as schemagen[7]. Both tools can be found in the bin folder of the java install location.
You can even generate an XSD programmaticaly from the JAXB annotated classes like shown in this example from stackoverflow[8].
If you are an eclipse user, this tutorial[9] from "the Open Tutorials" can give you first impressions into handling JAXB projects or creating JAXB classes from XSD or vice versa.
For the examples following was used: eclipse luna as IDE, java 7.25
JAXB Clients
The JAXB clients used for the examples here looks like:
// marshaller Marshaller m = jc.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); //handling CDATA - is described in adapter section XMLOutputFactory xof = XMLOutputFactory.newInstance(); XMLStreamWriter streamWriter = new CDataXMLStreamWriter( new IndentingXMLStreamWriter( xof.createXMLStreamWriter(new FileWriter(file)))); // unmarshaller JAXBContext jc = JAXBContext.newInstance(Person.class, Market.class, OtherPerson.class, Key.class, Value.class); Unmarshaller u = jc.createUnmarshaller(); outputObject = u.unmarshal(file);Since the examples are too large to be added on one page, I have split them on 3 pages.
Next pages
Simple-Sample-Example - Introduction to the common annotantion like @XmlRoot, @XmlAttribute, @XmlElementWrapper and @XmlType(propOrder = {"prop0", "prop1", and soon "}".
It's Adapter Time! - Using @XmlJavaTypeAdapter for maps and CDATA-Element.
Collective Polymorphism - Presenting Inheritance case with @XMLSeeAlso({SomeClass.class})
Everyday Situations and Résumé
Simple-Sample-Example
Empty constructor of every JAXB class is mandatory. By default all properties of one class are defined as if the where annotated with @XMLElement.@XmlRoot
The root class needed to be marked with the annotation @XMLRoot.
@XmlAttribute
If you want to change the property to become an attribute then it should be annotated with @XmlAttribute.
@XmlValue
Somethimes all properties of a class are attributes and one is the "special" value of the property. This can be realized with @XMLValue. @XmlValue - can be only used if the class have no @XMLElements. @XMLAttributes can occur.
@XmlElementWrapper
@XmlElementWrapper can be used only over collections and is the first and easiest solution as wrapper. If you need more informations in the wrapper element like attributes then you need to create JAXB class for that.
@XmlType(propOrder = {"prop0", "prop1", and soon "}"
If you want to order the properties of the class @XmlType(propOrder = {"prop0", "prop1", and soon "}" should be used. The names of the properties are the same like the getter methods without the "get"-prefix and not like the property names.
Given are the following classes:package name.stojanok.dzone.javaxml.situation1; 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(namespace = "name.stojanok.dzone") @XmlType(propOrder = { "id", "firstname", "lastname", "additionalInformation", "extraInformation" }) public class Person { private String id; private String firstname; private String lastname; private AdditionalInformation additionalInformation; private ExtraInformation extraInformation; public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } @XmlAttribute public String getId() { return id; } public void setId(String id) { this.id = id; } @XmlElement(name = "addInfo") public AdditionalInformation getAdditionalInformation() { return additionalInformation; } public void setAdditionalInformation( AdditionalInformation additionalInformations) { this.additionalInformation = additionalInformations; } public ExtraInformation getExtraInformation() { return extraInformation; } public void setExtraInformation(ExtraInformation extraInformation) { this.extraInformation = extraInformation; } } package name.stojanok.dzone.javaxml.situation1; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlValue; public class ExtraInformation { private String id; private String name; private String value; // for JAXB public ExtraInformation() { super(); } public ExtraInformation(String id, String name, String value) { super(); this.id = id; this.name = name; this.value = value; } @XmlAttribute public String getId() { return id; } public void setId(String id) { this.id = id; } @XmlAttribute public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlValue public String getValue() { return value; } public void setValue(String value) { this.value = value; } } package name.stojanok.dzone.javaxml.situation1; import java.util.ArrayList; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; public class AdditionalInformation { private boolean drivingLicense; private ArrayList<String> hobbies = new ArrayList<String>(); public boolean isDrivingLicense() { return drivingLicense; } public void setDrivingLicense(boolean drivingLicense) { this.drivingLicense = drivingLicense; } @XmlElementWrapper(name = "hobbies") @XmlElement(name = "hobby") public ArrayList<String> getHobbies() { return hobbies; } public void setHobbies(ArrayList<String> hobbies) { this.hobbies = hobbies; } }
The test methods:
@Test public void testMarshallerSituation1() { Person person = new Person(); person.setFirstname("John"); person.setLastname("Doe"); person.setId("123"); AdditionalInformation additionalInformations = new AdditionalInformation(); ArrayList<String> hobbies = new ArrayList<String>(Arrays.asList( "fishing", "techblog")); additionalInformations.setHobbies(hobbies); person.setAdditionalInformation(additionalInformations); ExtraInformation extraInformation = new ExtraInformation("987", "type", "info"); person.setExtraInformation(extraInformation); jaxbClient.marshaller(person, file1); } @Test public void testUnmarshallerSituation1() { Object object = jaxbClient.unmarshaller(file1); if (object instanceof Person) { LOGGER.trace("id: " + ((Person) object).getId()); LOGGER.trace("firstname: " + ((Person) object).getFirstname()); LOGGER.trace("lastname: " + ((Person) object).getLastname()); } }
The XML file:
<?xml version="1.0" ?> <ns2:person xmlns:ns2="name.stojanok.dzone" id="123"> <firstname>John</firstname> <lastname>Doe</lastname> <addInfo> <drivingLicense>false</drivingLicense> <hobbies> <hobby>fishing</hobby> <hobby>techblog</hobby> </hobbies> </addInfo> <extraInformation id="987" name="type">info</extraInformation> </ns2:person>
It's Adapter Time!
In some cases the normal annations are not leading to the right solution. In that case, the adapter must be written for solving that structure problem. I will describe two kind of cases where this situation comes.
Marshalling / Unmarshalling MapsSearching in the web for this case I found some good solutions:
- generic solution for this problem from the John Yearly's Blog[10]
- solution for a class which wraps array from the Sushant's Blog[11]
- solution for the given map (depend on the type of the key and value) written here on dzone by Blaise Doughan[12].
Here I will take the first generic and the last solution which is not generic but depends on the key, value types of the map.
@XmlJavaTypeAdapter(value=AdapterMap.class)
For the generic example I have used following unmodified classes from the John Yearly's Blog:
- XmlGenericMapAdapter
- MapType
- MapEntryType
For the type depended solution I have modified the class which can be found behind the link[12] above.
package name.stojanok.dzone.javaxml.situation2; import java.util.*; import javax.xml.bind.annotation.adapters.XmlAdapter; /** * taken from: * http://java.dzone.com/articles/jaxb-and-javautilmap * @author Blaise Doughan * * modified for my example * */ public class DependMapAdapter extends XmlAdapter<DependMapAdapter.AdaptedMap, Map<String, String>> { public static class AdaptedMap { public List<Entry> entry = new ArrayList<Entry>(); } public static class Entry { public String key; public String value; } @Override public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception { Map<String, String> map = new HashMap<String, String>(); for (Entry entry : adaptedMap.entry) { map.put(entry.key, entry.value); } return map; } @Override public AdaptedMap marshal(Map<String, String> map) throws Exception { AdaptedMap adaptedMap = new AdaptedMap(); for (Map.Entry<String, String> mapEntry : map.entrySet()) { Entry entry = new Entry(); entry.key = mapEntry.getKey(); entry.value = mapEntry.getValue(); adaptedMap.entry.add(entry); } return adaptedMap; } }
Given are the following classes:
package name.stojanok.dzone.javaxml.situation2; public class Key { private String key; public String getKey() { return key; } public void setKey(String key) { this.key = key; } } package name.stojanok.dzone.javaxml.situation2; public class Value { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } } package name.stojanok.dzone.javaxml.situation2; import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement public class OtherPerson { private Map<String, String> map0 = new HashMap<String, String>(); private Map<String, String> map1 = new HashMap<String, String>(); private Map<Key, Value> map3 = new HashMap<Key, Value>(); private String cdata; private String someData; @XmlJavaTypeAdapter(DependMapAdapter.class) public Map<String, String> getMap0() { return map0; } public void setMap0(Map<String, String> map0) { this.map0 = map0; } @XmlJavaTypeAdapter(XmlGenericMapAdapter.class) public Map<String, String> getMap1() { return map1; } public void setMap1(Map<String, String> map1) { this.map1 = map1; } @XmlJavaTypeAdapter(XmlGenericMapAdapter.class) public Map<Key, Value> getMap3() { return map3; } public void setMap3(Map<Key, Value> map3) { this.map3 = map3; } @XmlJavaTypeAdapter(CdataAdapter.class) public String getCdata() { return cdata; } public void setCdata(String cdata) { this.cdata = cdata; } public String getSomeData() { return someData; } public void setSomeData(String someData) { this.someData = someData; } }
Using CDATA
Annotation in JAXB for CDATA does not exist. One way to handle CDATA is using a selfmade Adapter. Some of the examples which I have found on internet are:
@XmlJavaTypeAdapter(value=CdataAdapter.class)
Using Java 6 and building the project with maven there was a problem with the restrictuon of the "com.sun.xml.internal.bind.characterEscapeHandler"-class. Workaround for that was to add the jaxb-impl jar to the classpath and to use the not internal "com.sun.xml.bind.characterEscapeHandler"-class.
Warning: Using this kind of modified "CharacterEscapeHandler" means that the signs which were escaped by default are not any more. This can lead to generating invalid XML files!!!
Better solution for CDATA
Other kind of solution for this problem can be done with implementing own XmlStream as described in the Michaels Blog[16].
With changing the pattern to CDATA, the custom XmlStream can be used for handling CDATA parts of the XMLStreams. One disadvantage is the flat format of the output. But that can be done by using the IndentingXMLStreamWriter. Because IndentingXMLStreamWriter belogs to the "internal" package it is recommended to use external jaxb-impl jar for building the project so you will not have any problems by building it with any build automation tool.
Both classes where used from the blog[16] where the first was not modified but the second is:
- DelegatingXMLStreamWriter
- CDataXMLStreamWriter
Given are the following classes:
package name.stojanok.dzone.javaxml.situation2; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter { private static final String CDATA_END = "]]>"; private static final String CDATA_BEGIN = "<![CDATA["; public CDataXMLStreamWriter(XMLStreamWriter del) { super(del); } @Override public void writeCharacters(String text) throws XMLStreamException { boolean useCData = false; if (text.startsWith(CDATA_BEGIN) && text.endsWith(CDATA_END)) { text = text.substring(CDATA_BEGIN.length(), text.length() - CDATA_END.length()); useCData = true; } if (useCData) { super.writeCData(text); } else { super.writeCharacters(text); } } } package name.stojanok.dzone.javaxml.situation2; import javax.xml.bind.annotation.adapters.XmlAdapter; public class CdataAdapter extends XmlAdapter<String, String> { private static final String CDATA_END = "]]>"; private static final String CDATA_BEGIN = "<![CDATA["; @Override public String marshal(String v) throws Exception { return CDATA_BEGIN + v + CDATA_END; } @Override public String unmarshal(String v) throws Exception { if (v.startsWith(CDATA_BEGIN) && v.endsWith(CDATA_END)) { v = v.substring(CDATA_BEGIN.length(), v.length() - CDATA_END.length()); } return v; } }
The test methods look this way:
@Test public void testMarshallerSituation2() { OtherPerson otherPerson = new OtherPerson(); Map<String, String> map = new HashMap<String, String>(); map.put("property0", "value0"); map.put("property1", "value1"); otherPerson.setMap0(map); otherPerson.setMap1(map); Map<Key, Value> map0 = new HashMap<Key, Value>(); Key key = new Key(); Value value = new Value(); key.setKey("key0"); value.setValue("value0"); map0.put(key, value); Key key0 = new Key(); Value value0 = new Value(); key0.setKey("key1"); value0.setValue("value2"); map0.put(key0, value0); otherPerson.setMap3(map0); otherPerson.setCdata("cdata conent <html></html>"); otherPerson.setSomeData("a & b"); jaxbClient.marshaller(otherPerson, file2); } @Test public void testUnmarshallerSituation2() { Object object = jaxbClient.unmarshaller(file2); if (object instanceof OtherPerson) { OtherPerson otherPerson = (OtherPerson)object; for (Entry<String, String> entry : otherPerson.getMap0().entrySet()) { LOGGER.trace(entry); } for (Entry<String, String> entry : otherPerson.getMap1().entrySet()) { LOGGER.trace(entry); } LOGGER.trace(otherPerson.getCdata()); } }
The XML file:
<?xml version="1.0" ?> <otherPerson xmlns:ns2="name.stojanok.dzone"> <cdata><![CDATA[cdata conent <html></html>]]></cdata> <map0> <entry> <key>property1</key> <value>value1</value> </entry> <entry> <key>property0</key> <value>value0</value> </entry> </map0> <map1> <entry> <key xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">prperty1</key> <value xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value1</value> </entry> <entry> <key xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">prperty0</key> <value xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value0</value> </entry> </map1> <map3> <entry> <key xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="key"> <key>key1</key> </key> <value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="value"> <value>value2</value> </value> </entry> <entry> <key xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="key"> <key>key0</key> </key> <value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="value"> <value>value0</value> </value> </entry> </map3> <someData>a & b</someData> </otherPerson>
Collective Polymorphism
Using different objects in a collection which are in an inheritance relationship can be very tricky if you don't know the right annotation. Here I want to show you an example where this situation is implemented.@XMLSeeAlso({SomeClass.class})
Given are the following classes:
package name.stojanok.dzone.javaxml.situation3; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Market { private List<Fruit> fruits; @XmlElementWrapper(name = "fruits") @XmlElement(name = "fruit") public List<Fruit> getFruits() { return fruits; } public void setFruits(List<Fruit> fruits) { this.fruits = fruits; } } package name.stojanok.dzone.javaxml.situation3; import javax.xml.bind.annotation.XmlSeeAlso; @XmlSeeAlso({Apple.class, Pear.class}) public class Fruit { private String color; private String type; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getType() { return type; } public void setType(String type) { this.type = type; } } package name.stojanok.dzone.javaxml.situation3; public class Apple extends Fruit { private String appleProperty; public String getAppleProperty() { return appleProperty; } public void setAppleProperty(String appleProperty) { this.appleProperty = appleProperty; } } package name.stojanok.dzone.javaxml.situation3; public class Pear extends Fruit { private String pearProperty; public String getPearProperty() { return pearProperty; } public void setPearProperty(String pearProperty) { this.pearProperty = pearProperty; } }
The test methods:
@Test public void testMarshallerSituation3() { Market market = new Market(); List<Fruit> fruits = new ArrayList<Fruit>(); Apple apple = new Apple(); apple.setColor("red"); apple.setType("type1"); apple.setAppleProperty("some"); fruits.add(apple); Pear pear = new Pear(); pear.setColor("yellow"); pear.setType("type2"); pear.setPearProperty("some"); fruits.add(pear); market.setFruits(fruits); jaxbClient.marshaller(market, file3); } @Test public void testUnmarshallerSituation3() { Object object = jaxbClient.unmarshaller(file3); if (object instanceof Market) { Market market = ((Market)object); for (Fruit fruit : market.getFruits()) { if (fruit instanceof Apple) { LOGGER.trace("apple: " + fruit.getColor()); } else if (fruit instanceof Pear) { LOGGER.trace("pear: " + fruit.getColor()); } } } }
The XML file:
<?xml version="1.0" ?> <market xmlns:ns2="name.stojanok.dzone"> <fruits> <fruit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="apple"> <color>red</color> <type>type1</type> <appleProperty>some</appleProperty> </fruit> <fruit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pear"> <color>yellow</color> <type>type2</type> <pearProperty>some</pearProperty> </fruit> </fruits> </market>
Everyday Situations
Hint
One of my tasks was to create an XML file and to convert it via unmarshalling to a Java object. Doing this, I got a situation where some of the data were not found into the object. Searching for the cause (i.e forgetting @XmlAttribute, typo of the propety and other cases) can be difficult and confusing. Another approach to find the cause is to create the XML from the annotated class with marshalling. With that "other direction" way, you can see where the differences between the generated (output) XML file and the expected (input) XML are.
Unit-Test's are good practice for testing every change of the JAXB clasess.
Problems
I had a situation while the JAXB part of the application had a problem, more precisely, exception was thrown only by executing it with the JVM of Java 7. In Java 6 everything was fine. It was primitive boolean property of a class which was not initialized explicitly. Doing that solved the problem for that case.
Another problem caused the implementation of the adapter class for the Map. In OpenJDK JVM exception was thrown by one method. Implementing as a method that has overloaded the original method solved the problem here.
You can see that there are still some differences between the JVM where there shouldn't be. But with searching the internet for bug issues and exception messages, and/or creating workarounds for those strange situations, the problems were solved.
Résumé
JAXB is a nice technology for handling XML files. Unmarshalling an XML file creates an object with all data restored from the file only by using the unmarshaller handler. This way you dont need to search for the content explicitly like doing it with the parsers of the JAXP API.
Using only Unmarshaing
If you are using this processing method only for unmarshaling than you should use the hint mentioned above in this article. For this use case for a complex XML structure many annotated classes will be needed and at first this will look unnesessery oversized and disadvantaging.
Using only Marshalling
If the XML structure is derived from the model of the application, then you are not creating some extra resources and waste time for it. Other is the situation when the structure of the model doesn't match the XML output. Then you do need to create extra classes only to make the XML structure like it should be.
Marshalling and UnmarshallingBest case is when you need less extra work in your project for this kind of processing. Then using the handlers for both JAXB methods are needed. Somethimes you need only part of global structure like persisting the object deeper in the class structure like some object reference. That is also possible, but then you get the same class via unmarshalling back. More work is needed when the persistence structure is not the same like the model of the application. Then extra work is needed for creating the classes to make the XML structure like specified.
Last word
Writing this article, I have noticed that this kind of processing needs more complex explanation than the JAXP API, because the approach of this kind of processing is bound to the model classes which can be used as the annotated classes for the JAXB, but don't have to be. The question is, does the class need to be used for this aspect or not? I think this question is used always when you need to use a class for other aspects like binding them to JAXB, JPA and other persistence output or other boundary, framework, or whatever else library or application.
Searching on the web for information for this article I found a link about EclipseLink JAXB (MOXy)'s External Mapping Document[17]. This way you can define the mapping without annotations. It was used in cases where the classes which are used for XML bindings are forbidden to be changed. Knowing this option, you have one more choice if you want to use annotation on your model classes or external mapping definition or not.
I hope I could give you summary about JAXB with the most useful annotations. Also some hints and problems were mentioned and use cases discussed. If I come to new situations and have enough motivation to write, I will write new article about that.
GitHub
The sources for this example can be checked out from: https://github.com/kstojanovski/java-and-xml. Use the java-and-xml-jaxb folder and start the test method of the TestJaxb.java file from the test folder.
Opinions expressed by DZone contributors are their own.
Comments