The ‘object-relational impedance mismatch’ is a well documented set of conceptual and technical difficulties that are often encountered when a relational database management system is being used by a program written in an object-oriented programming language. A similar impedance mismatch exists when XML is used by a program written in an object-oriented programming language.
Many popular ‘Object-Relational Mapping’ (ORM) frameworks exist that address the object-relational impedance mismatch and no doubt helped to inspire the evolution of ‘Object-XML Mapping’ (OXM) frameworks to address the object-xml impedance mismatch.
For the Java community, there are a number of OXM frameworks from which to choose (Castor, XStream, JiBX, JAXB) with each having particular strengths and weaknesses. The standard OXM framework for Java is JAXB.
In the following I work through a simple example that demonstrates object to XML marshalling (and demarshalling) using Spring, Spring WS and JAXB (and later JAXB Introductions!)
I have provided Maven projects for download so you can follow along:
Spring OXM and JAXB Source Download
Spring OXM, JAXB and JAXB Introductions Download
The Model
The model object that I have used for this example is a simple object representing a ‘Person’. The class contains only simple attributes.
package net.thoughtforge.model;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Calendar;
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;
@XmlRootElement(
name="Person",
namespace="http://thoughtforge.net/model")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
public class Person implements Serializable {
private static final long serialVersionUID = 8465162879793776395L;
@XmlElement(namespace="http://thoughtforge.net/model")
private Calendar dateOfBirth;
@XmlElement(namespace="http://thoughtforge.net/model")
private String firstName;
@XmlElement(namespace="http://thoughtforge.net/model")
private BigDecimal height;
@XmlElement(namespace="http://thoughtforge.net/model")
private String lastName;
@XmlElement(namespace="http://thoughtforge.net/model")
private BigDecimal weight;
public Calendar getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Calendar dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public BigDecimal getHeight() {
return height;
}
public void setHeight(BigDecimal height) {
this.height = height;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public BigDecimal getWeight() {
return weight;
}
public void setWeight(BigDecimal weight) {
this.weight = weight;
}
}
Listing 1 : Person.java
As you can see from the listing, the class is annotated with JAXB annotations that are fairly self explanatory. I will leave it to you to look up the precise definition and consequence of these annotations (which can be found on the JAXB Reference Implementation website).
The XML Schema (XSD)
JAXB does not require an XML schema (a default schema can be generated), but for this example (and always on commercial projects) I have explicitly specified a schema in order to include type restrictions. It is important to remember that the XSD forms the contract between XML producers and consumers (marshaller and unmarshaller) and should be as detailed as possible.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://thoughtforge.net/model" xmlns:tns="http://thoughtforge.net/model" attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0"> <xsd:element name="Person" type="tns:Person"/> <xsd:complexType name="Person"> <xsd:sequence> <xsd:element name="dateOfBirth" type="xsd:dateTime"/> <xsd:element name="firstName"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:maxLength value="50"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="height"> <xsd:simpleType> <xsd:restriction base="xsd:decimal"> <xsd:fractionDigits value="2"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="lastName"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:maxLength value="50"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="weight"> <xsd:simpleType> <xsd:restriction base="xsd:decimal"> <xsd:fractionDigits value="2"/> </xsd:restriction> </xsd:simpleType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:schema>
Listing 2 : person.xsd
The Marshaller Abstraction
Rather than use the Spring Marshaller/Unmarshaller interface directly, I often use an abstraction for the Marshaller. The abstraction I used here is very simple and of course would not be practical for use with large XML files as it does not support streaming etc.
package net.thoughtforge.marshaller;
public interface Marshaller {
String marshal(Object object);
Object unmarshal(String string);
}
Listing 3 : Marshaller.java
The implementation of the marshaller delegates to a Spring JAXB marshaller.
package net.thoughtforge.marshaller;
import java.io.StringWriter;
import javax.xml.transform.stream.StreamResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.xml.transform.StringSource;
@Component(value="marshaller")
public class Jaxb2Marshaller implements Marshaller {
@Autowired
@Qualifier(value="jaxb2Marshaller")
private org.springframework.oxm.jaxb.Jaxb2Marshaller marshaller;
public String marshal(Object object) {
final StringWriter out = new StringWriter();
marshaller.marshal(object, new StreamResult(out));
return out.toString();
}
public Object unmarshal(String string) {
return marshaller.unmarshal(new StringSource(string));
}
}
Listing 4 : Jaxb2Marshaller.java
The Configuration
Using Spring to define a JAXB marshaller is relatively straight forward as the listing shows. I have specified the classes to be bound and the XML schema. If the XML schema is specified, the marshaller will instruct the XML parser to validate XML against the schema.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="net.thoughtforge.marshaller"/> <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="classesToBeBound"> <list> <value>net.thoughtforge.model.Person</value> </list> </property> <property name="schema" value="classpath:schema/person.xsd"/> </bean> </beans>
Listing 5 : applicationContext-marshaller.xml
The Test
The following test is not exhaustive but demonstrates the marshalling and unmarshalling of a Person object. It also demonstrates XML schema validation occurring during the marshalling and unmarshalling process.
package net.thoughtforge.marshaller;
import java.math.BigDecimal;
import java.util.Calendar;
import net.thoughtforge.model.Person;
import org.apache.commons.lang.text.StrBuilder;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.oxm.MarshallingFailureException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:applicationContext/applicationContext-*.xml"})
public class Jaxb2MarshallerTest {
private static final String MARSHALLED_PERSON =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Person xmlns=\"http://thoughtforge.net/model\"><dateOfBirth>1965-01-01T00:00:00Z</dateOfBirth><firstName>Joe</firstName><height>1.85</height><lastName>Bloggs</lastName><weight>12.2</weight></Person>";
private static Calendar dateOfBirth;
private static String firstName;
private static BigDecimal height;
private static String lastName;
private static BigDecimal weight;
@Autowired
@Qualifier(value="marshaller")
private Jaxb2Marshaller marshaller;
@BeforeClass
public static void beforeClass() {
dateOfBirth = Calendar.getInstance();
dateOfBirth.clear();
dateOfBirth.set(Calendar.DATE, 1);
dateOfBirth.set(Calendar.MONTH, Calendar.JANUARY);
dateOfBirth.set(Calendar.YEAR, 1965);
firstName = "Joe";
height = new BigDecimal("1.85");
lastName = "Bloggs";
weight = new BigDecimal("12.2");
}
@Test
public void marshallPerson() {
Person person = new Person();
person.setDateOfBirth(dateOfBirth);
person.setFirstName(firstName);
person.setHeight(height);
person.setLastName(lastName);
person.setWeight(weight);
String xml = marshaller.marshal(person);
Assert.assertNotNull(xml);
Assert.assertEquals(MARSHALLED_PERSON, xml);
}
@Test
public void marshallPersonInvalidFirstName() {
Person person = new Person();
person.setDateOfBirth(dateOfBirth);
person.setFirstName(new StrBuilder(firstName).appendPadding(50, '0').toString());
person.setHeight(height);
person.setLastName(lastName);
person.setWeight(weight);
try {
marshaller.marshal(person);
Assert.fail("First name length restriction not applied.");
} catch (MarshallingFailureException marshallingFailureException) {
Throwable rootCause = marshallingFailureException.getRootCause();
Assert.assertFalse(rootCause.getMessage().indexOf("is not facet-valid with respect to maxLength '50'") == -1);
}
}
@Test
public void marshallPersonInvalidHeight() {
Person person = new Person();
person.setDateOfBirth(dateOfBirth);
person.setFirstName(firstName);
person.setHeight(height.add(new BigDecimal("0.1111")));
person.setLastName(lastName);
person.setWeight(weight);
try {
marshaller.marshal(person);
Assert.fail("Height precision restriction not applied.");
} catch (MarshallingFailureException marshallingFailureException) {
Throwable rootCause = marshallingFailureException.getRootCause();
Assert.assertFalse(rootCause.getMessage().indexOf("the number of fraction digits has been limited to 2") == -1);
}
}
@Test
public void marshallPersonInvalidLastName() {
Person person = new Person();
person.setDateOfBirth(dateOfBirth);
person.setFirstName(firstName);
person.setHeight(height);
person.setLastName(new StrBuilder(lastName).appendPadding(50, '0').toString());
person.setWeight(weight);
try {
marshaller.marshal(person);
Assert.fail("First name length restriction not applied.");
} catch (MarshallingFailureException marshallingFailureException) {
Throwable rootCause = marshallingFailureException.getRootCause();
Assert.assertFalse(rootCause.getMessage().indexOf("is not facet-valid with respect to maxLength '50'") == -1);
}
}
@Test
public void marshallPersonInvalidWeight() {
Person person = new Person();
person.setDateOfBirth(dateOfBirth);
person.setFirstName(firstName);
person.setHeight(height);
person.setLastName(lastName);
person.setWeight(weight.add(new BigDecimal("0.1111")));
try {
marshaller.marshal(person);
Assert.fail("Weight precision restriction not applied.");
} catch (MarshallingFailureException marshallingFailureException) {
Throwable rootCause = marshallingFailureException.getRootCause();
Assert.assertFalse(rootCause.getMessage().indexOf("the number of fraction digits has been limited to 2") == -1);
}
}
@Test
public void unmarshallPerson() {
Person person = (Person) marshaller.unmarshal(MARSHALLED_PERSON);
Assert.assertNotNull(person);
Assert.assertTrue(dateOfBirth.compareTo(person.getDateOfBirth()) == 0);
Assert.assertEquals(firstName, person.getFirstName());
Assert.assertEquals(height, person.getHeight());
Assert.assertEquals(lastName, person.getLastName());
Assert.assertEquals(weight, person.getWeight());
}
}
Listing 6 : Jaxb2MarshallerTest.java
Removing JAXB Annotations
If you are using the reference implementation of JAXB you are required to use annotations to map objects to XML. As I mentioned in a previous post, I’m not a fan of using annotations for the purpose of mapping objects to some other format (relational database or XML or A.N. Other). I find it creates code clutter especially in instances were entity model classes are mapped to both a relational database and XML (using JAXB). In modular projects, it also creates unnecessary dependencies on the model module.
Luckily, some bright spark came along and created JAXB Introductions which allows you to define the mapping of objects to XML in an XML file. I’m not going to regurgitate the information on the JAXB Introductions website but I will outline the modifications I had to make to allow me to use JAXB Introductions with the example above.
First, I added the maven dependency to the project.
<dependency> <groupId>jboss.jaxbintros</groupId> <artifactId>jboss-jaxb-intros</artifactId> <version>1.0.1.GA</version> </dependency>
Listing 7 : JAXB Introductions Maven Dependency
Second, I created a JAXB Introductions mapping file.
<?xml version = "1.0" encoding = "UTF-8"?> <jaxb-intros xmlns="http://www.jboss.org/xsd/jaxb/intros"> <Class name="net.thoughtforge.model.Person"> <XmlType/> <XmlRootElement name="Person" namespace="http://thoughtforge.net/model"/> <XmlAccessorType value="FIELD"/> <Field name="dateOfBirth"> <XmlElement name="dateOfBirth" namespace="http://thoughtforge.net/model"/> </Field> <Field name="firstName"> <XmlElement name="firstName" namespace="http://thoughtforge.net/model"/> </Field> <Field name="height"> <XmlElement name="height" namespace="http://thoughtforge.net/model"/> </Field> <Field name="lastName"> <XmlElement name="lastName" namespace="http://thoughtforge.net/model"/> </Field> <Field name="weight"> <XmlElement name="weight" namespace="http://thoughtforge.net/model"/> </Field> </Class> </jaxb-intros>
Listing 8 : marshaller-mapping.xml
Thirdly, I modified the Spring configuration to inject the JAXB Introductions annotation reader into the JAXB marshaller.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="net.thoughtforge.marshaller"/> <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="classesToBeBound"> <list> <value>net.thoughtforge.model.Person</value> </list> </property> <property name="jaxbContextProperties"> <map> <entry> <key> <util:constant static-field="com.sun.xml.bind.api.JAXBRIContext.ANNOTATION_READER"/> </key> <bean class="org.jboss.jaxb.intros.IntroductionsAnnotationReader"> <constructor-arg ref="jaxbIntroductions"/> </bean> </entry> </map> </property> <property name="schema" value="classpath:schema/person.xsd"/> </bean> <bean id="jaxbIntroductions" class="org.jboss.jaxb.intros.IntroductionsConfigParser" factory-method="parseConfig"> <constructor-arg><value>classpath:marshaller-mapping.xml</value></constructor-arg> </bean> </beans>
Listing 9 : applicationContext-marshaller.xml
Finally, I removed the JAXB annotations from the model class (Person.java).
package net.thoughtforge.model;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Calendar;
public class Person implements Serializable {
private static final long serialVersionUID = 8465162879793776395L;
private Calendar dateOfBirth;
private String firstName;
private BigDecimal height;
private String lastName;
private BigDecimal weight;
public Calendar getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Calendar dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public BigDecimal getHeight() {
return height;
}
public void setHeight(BigDecimal height) {
this.height = height;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public BigDecimal getWeight() {
return weight;
}
public void setWeight(BigDecimal weight) {
this.weight = weight;
}
}
Listing 10 : Person.java
Hopefully, this short example will prove useful for those starting out with JAXB and OXM. For those already familiar with JAXB and OXM, perhaps it has shown you how you can use JAXB without annotations.



So where is the Spring WS part in your samples?
Hi Alex,
At the time of writing the Spring OXM functionality was part of the Spring WS project (I believe it still is).
John
Hi John,
What I meant is where are the web service endponts/ports/etc specified in your code?
From what I see you’ve got JaxB marshalling/unmarshalling working, but it’s not tied with Spring-WS.
Thank you!
Hi Alex,
I have only demonstrated the marshalling aspect and have no WS endpoints defined. I will do another post on demonstrating that in a few weeks (if that would help?). I’m a little tied up with coursework etc. at the moment.
Hi John, thanks for the helpful post! I wonder what the Spring-WS configuration should look like if I am using the marshaller tag
and need to have some classes auto-generated while reusing a few existing classes, such as enums, etc?
Thanks,
Alec
Oops… The tag I meant to include:
<oxm:jaxb2-marshaller id=”marshaller” contextPath=”…”>
I’m not sure how you could mix the two configurations (Annotation and Introduction XML). I think you will have to define all mappings in one or other.
If you use introductions, the marshaller will ignore the JAXB annotations on ‘existing classes’.
Right now I have an XSD that defines a contract. I do not use annotations in my existing classes, but I am using annotated endpoints (@Endpoint, etc.) The schema objects are generated, and I map them via a utility class to my domain objects. everything works fine. However, some mappings get tedious and seems redundant when the generated objects are all but identical to the original domain objects I already have. I wonder if there is a best practice for handling these situations, and/or an easy way to let JAXB know that some elements must be bound to existing Java classes instead of the schema-generated ones?