Home | FAQ | Contact me

Thoughtworks XStream example

Here's a fairly complete example of using Thoughtworks' XStream library to serialize a POJO to XML, then reverse the process. The very calls into this library are highlighted. The test case here represents the simple, basic uses of XStream to serialize a POJO into XML and deserialize (the same XML string) back into a POJO.

XStreamTest.java:
package com.windofkeltia.xstream;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

import org.junit.Test;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.StaxDriver;

public class XStreamTest
{
  @Test
  public void test()
  {
    XStream xstream = new XStream( new StaxDriver() );
    Student student = getTestDetails();

    // object (POJO) to XML conversion...
    String xml = xstream.toXML( student );
    System.out.println( "Serialization of class Student:\n" + xml );
    System.out.println();
    System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) );

    // XML back to object conversion...
    Student student1 = ( Student ) xstream.fromXML( xml );
    System.out.println( "Deserialization of XML into POJO:\n" + student1 );
  }

  private Student getTestDetails()
  {
    Student student = new Student();
    student.setFirst( "Lily" );
    student.setLast ( "Munster" );
    student.setId   ( 666 );
    student.setYear ( "4th" );
    student.setGpa  ( 3.43 );

    Address address = new Address();
    address.setStreet ( "1313 Mockingbird Lane" );
    address.setCity   ( "Mockingbird Heights" );
    address.setState  ( "CA" );
    address.setCountry( "US" );
    address.setZipcode( 90210 );

    student.setAddress( address );
    return student;
  }

  public static String formatXml( String xml )
  {
    try
    {
      Transformer serializer = SAXTransformerFactory.newInstance().newTransformer();

      serializer.setOutputProperty( OutputKeys.INDENT, "yes" );
      serializer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2" );

      Source       source = new SAXSource( new InputSource( new ByteArrayInputStream( xml.getBytes() ) ) );
      StreamResult res    = new StreamResult( new ByteArrayOutputStream() );

      serializer.transform( source, res );

      // make this prettier...
      String result      = new String( ( ( ByteArrayOutputStream ) res.getOutputStream() ).toByteArray() );
      int    endOfHeader = result.indexOf( "?>" ) + "?>".length();
      return result.substring( 0, endOfHeader ) + '\n' + result.substring( endOfHeader, result.length() );
    }
    catch( Exception e )
    {
      return xml;
    }
  }
}

Here are the Student and Address POJOs:

Student.java:
package com.windofkeltia.xstream;

public class Student
{
  private int     id;
  private String  first;
  private String  last;
  private String  year;
  private Double  gpa;
  private Address address;

  public int     getId()      { return id; }
  public String  getFirst()   { return first; }
  public String  getLast()    { return last; }
  public String  getYear()    { return year; }
  public Double  getGpa()     { return gpa; }
  public Address getAddress() { return address; }

  public void    setId     ( int id )          { this.id = id; }
  public void    setFirst  ( String first )    { this.first = first; }
  public void    setLast   ( String last )     { this.last = last; }
  public void    setYear   ( String year )     { this.year = year; }
  public void    setGpa    ( Double gpa )      { this.gpa = gpa; }
  public void    setAddress( Address address ) { this.address = address; }

  public String toString()
  {
    StringBuilder sb = new StringBuilder();

    sb.append( "{\n" );
    sb.append( "       id: "  ).append( id ).append( '\n' );
    sb.append( "    first: "  ).append( first ).append( '\n' );
    sb.append( "     last: "  ).append( last ).append( '\n' );
    sb.append( "     year: "  ).append( year ).append( '\n' );
    sb.append( "      gpa: "  ).append( gpa ).append( '\n' );
    sb.append( "  address:\n" ).append( address.toString( 1 ) );
    sb.append( "}\n" );

    return sb.toString();
  }
}
Address.java:
package com.windofkeltia.xstream;

public class Address
{
  private String street;
  private String city;
  private String state;
  private String country;
  private int    zipcode;

  public String getStreet() { return street; }
  public String getCity() { return city; }
  public String getState() { return state; }
  public String getCountry() { return country; }
  public int    getZipcode() { return zipcode; }

  public void   setStreet( String street ) { this.street = street; }
  public void   setCity( String city ) { this.city = city; }
  public void   setState( String state ) { this.state = state; }
  public void   setCountry( String country ) { this.country = country; }
  public void   setZipcode( int zipcode ) { this.zipcode = zipcode; }

  public String toString()
  {
    return toString( 0 );
  }

  public String toString( int indentLevel )
  {
    StringBuilder sb = new StringBuilder();
    String        tab = "";

    while( indentLevel-- > 0 )
      tab += "  ";

    sb.append( tab ).append( "{\n" );
    sb.append( tab ).append( "   street: " ).append( street ).append( '\n' );
    sb.append( tab ).append( "     city: " ).append( city ).append( '\n' );
    sb.append( tab ).append( "    state: " ).append( state ).append( '\n' );
    sb.append( tab ).append( "  country: " ).append( country ).append( '\n' );
    sb.append( tab ).append( "  zipcode: " ).append( zipcode ).append( '\n' );
    sb.append( tab ).append( "}\n" );

    return sb.toString();
  }
}

Output from this test:

<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...

(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
  <id>666</id>
  <first>Lily</first>
  <last>Munster</last>
  <year>4th</year>
  <gpa>3.43</gpa>
  <address>
    <street>1313 Mockingbird Lane</street>
    <city>Mockingbird Heights</city>
    <state>CA</state>
    <country>US</country>
    <zipcode>90210</zipcode>
  </address>
</com.windofkeltia.xstream.Student>

Deserialization of XML into POJO:
{
       id: 666
    first: Lily
     last: Munster
     year: 4th
      gpa: 3.43
  address:
  {
     street: 1313 Mockingbird Lane
       city: Mockingbird Heights
      state: CA
    country: US
    zipcode: 90210
  }
}

Maven dependency

<dependency>
  <groupId>com.thoughtworks.xstream</groupId>
  <artifactId>xstream</artifactId>
  <version>1.4.12</version>
</dependency>

Aberrations

What if we short the XML input or add to or change it contrary to the POJO? Let's say the XML comes in with Thoughtworks' usually description:

<com.windofkeltia.xstream.Student>

...claiming to be a Student, but missing, adding or exchanging a field. The following examples will help us understand this.

  1. Remove (lose, fail to include, etc.) a field from the XML serialization that was in the POJO.
  2. Remove a field from the XML serialization, but add a new field that isn't in the POJO to the XML serialization before deserialization.
  3. Add a new field that isn't in the POJO to the XML serialization before deserialization.

1. Let's test removing completely a field from the XML string. Add this test case and supporting code to XStreamTest.java:

@Test
public void testMissingFieldFromOriginalPojo()
{
  XStream xstream = new XStream( new StaxDriver() );
  Student student = getTestDetails();

  // Object to XML conversion
  String xml = xstream.toXML( student );

  xml = removeStateFieldFromStudent( xml );

  System.out.println( "Serialization of class Student:\n" + xml );
  System.out.println();
  System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) );

  // XML to Object conversion
  Student student1 = ( Student ) xstream.fromXML( xml );
  System.out.println( "Deserialization of XML into POJO:\n" + student1 );
}

/**
 * Smoke the state from the address to see what happens.
 */
private String removeStateFieldFromStudent( final String xml )
{
  final String STATE_ELEMENT = "<state>CA</state>";
  int length                 = STATE_ELEMENT.length();
  int position               = xml.indexOf( STATE_ELEMENT );
  return xml.substring( 0, position ) + xml.substring( position+length, xml.length() );
}

Now, with the <state> field removed, let's see what the resulting output is. Note that no exception is thrown, the field is missing from the XML string as well as being null in the POJO. Because the POJO's toString() method doesn't guard against null, it prints out that way:

<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...

(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
  <id>666</id>
  <first>Lily</first>
  <last>Munster</last>
  <year>4th</year>
  <gpa>3.43</gpa>
  <address>
    <street>1313 Mockingbird Lane</street>
    <city>Mockingbird Heights</city>
    <country>US</country>
    <zipcode>90210</zipcode>
  </address>
</com.windofkeltia.xstream.Student>

Deserialization of XML into POJO:
{
       id: 666
    first: Lily
     last: Munster
     year: 4th
      gpa: 3.43
  address:
  {
     street: 1313 Mockingbird Lane
       city: Mockingbird Heights
      state: null
    country: US
    zipcode: 90210
  }
}

2. Next, let's do something more vicious. Not only will we remove the state field, we'll add a new field not defined in the POJO, <province>.

@Test
public void testAddingFieldsBeyondOriginalPojo()
{
  XStream xstream = new XStream( new StaxDriver() );
  Student student = getTestDetails();

  // Object to XML conversion
  String xml = xstream.toXML( student );

  xml = removeStateFieldFromStudent( xml );
  xml = addProvinceFieldToStudent( xml );

  System.out.println( "Serialization of class Student:\n" + xml );
  System.out.println();
  System.out.println( "(Pretty-printed:)\n" + formatXml( xml ) );

  // XML to Object conversion
  Student student1 = null;

  try
  {
    student1 = ( Student ) xstream.fromXML( xml );
  }
  catch( Exception e )
  {
    e.printStackTrace();
  }

  System.out.println( "Deserialization of XML into POJO:\n" + student1 );
}

/**
 * Add the province in place of the state in the address to see what happens.
 */
private String addProvinceFieldToStudent( final String xml )
{
  final String PROVINCE_ELEMENT = "<province>BC</province>";
  final String COUNTRY_ELEMENT  = "<country>US</country";
  int length                    = COUNTRY_ELEMENT.length();
  int position                  = xml.indexOf( COUNTRY_ELEMENT );
  return xml.substring( 0, position )
      + PROVINCE_ELEMENT
      + xml.substring( position+length+1, xml.length() );
}
<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...

(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
  <id>666</id>
  <first>Lily</first>
  <last>Munster</last>
  <year>4th</year>
  <gpa>3.43</gpa>
  <address>
    <street>1313 Mockingbird Lane</street>
    <city>Mockingbird Heights</city>
    <province>BC</province>
    <zipcode>90210</zipcode>
  </address>
</com.windofkeltia.xstream.Student>

com.thoughtworks.xstream.converters.ConversionException: province : province
---- Debugging information ----
message             : province : province
cause-exception     : com.thoughtworks.xstream.mapper.CannotResolveClassException
cause-message       : province : province
class               : com.windofkeltia.xstream.Student
required-type       : com.windofkeltia.xstream.Address
path                : /com.windofkeltia.xstream.Student/address/province
line number         : 1
-------------------------------
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:63)
    at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:45)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:46)
    at com.thoughtworks.xstream.annotations.AnnotationReflectionConverter.unmarshallField(AnnotationReflectionConverter.java:66)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:188)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:125)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:56)
    at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:45)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:46)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:117)
    at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy.unmarshal(ReferenceByXPathMarshallingStrategy.java:29)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:846)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:833)
    at com.thoughtworks.xstream.XStream.fromXML(XStream.java:781)
    at com.thoughtworks.xstream.XStream.fromXML(XStream.java:773)
    at com.windofkeltia.xstream.XStreamTest.testAddingFieldsBeyondOriginalPojo(XStreamTest.java:120)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.thoughtworks.xstream.mapper.CannotResolveClassException: province : province
    at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:49)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.ClassAliasingMapper.realClass(ClassAliasingMapper.java:76)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.DynamicProxyMapper.realClass(DynamicProxyMapper.java:60)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.ArrayMapper.realClass(ArrayMapper.java:76)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:26)
    at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:34)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.determineType(AbstractReflectionConverter.java:296)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:178)
    at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:125)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:56)
Deserialization of XML into POJO:
null

3. Last, let's just add <province> without removing the state field. The result is similar to the last case, the exception is thrown. You see this

<?xml version="1.0" ?><com.windofkeltia.xstream.Student><id>666</id><first>Lily</first><last>Munster...

(Pretty-printed:)
<?xml version="1.0" encoding="UTF-8"?>
<com.windofkeltia.xstream.Student>
  <id>666</id>
  <first>Lily</first>
  <last>Munster</last>
  <year>4th</year>
  <gpa>3.43</gpa>
  <address>
    <street>1313 Mockingbird Lane</street>
    <city>Mockingbird Heights</city>
    <state>CA</state>
    <province>BC</province>
    <zipcode>90210</zipcode>
  </address>
</com.windofkeltia.xstream.Student>

(exception here just as before)

Conclusion

Leave fields out of the serialized XML, no problem—they'll be shown to be unset in the deserialized POJO and, depending on how crucial they are, you'll need to check them before use.

Add a new field that's not in the POJO will result in the code throwing an exception when deserialized from XML (fromXML() method).