Luigi is a passionate software developer working in Java since 2000. In 2001 he co-founded the Java User Group Torino, the first local JUG in Italy. Quickly after that, many other JUGs have born in Italy following JUG Torino's example. Since some years now, he is working as free lance software consultant between Italy, Germany, UK and Switzerland, on several industries. His personal interests include software, science fiction books and movies, traveling, photography and good food. Luigi is a DZone MVB and is not an employee of DZone and has posted 19 posts at DZone. You can read more from them at their website. View Full User Profile

JavaBeans to XML, With No Libraries

08.16.2010
| 11618 views |
  • submit to reddit

Converting JavaBeans to XML and viceversa is quite a common task, and there are tons of libraries around for this purpose. But I always like to use what is already available in the JRE, avoiding dependencies as much as possible. In the past I and Simone developed a rest API using the JSR57 serialization, that was already available in the Java 5. But Java 6 included JAXB in the standard libraries, which is more modern and flexible, and gives you a better control over the XML translation process.

Here I want to provide a couple of simple examples to show how those two APIs work.

Suppose we have some java beans "the model" as per the following source:

public class Book implements Serializable {
String title;
String author;
Price price;
public Book() {} //default constructor is mandatory for JavaBeans

public Book(String title, String author, Price price) {
this.title= title;
this.author = author;
this.price = price;
}

//... imagine the getter and setters boilerplate code here ...

@Override
public String toString() {
return title + " by " + author + ", " + price;
}

}

public class Price implements Serializable {
Double amount;
Currency currency;

public Price() {} // default constructor, as JavaBeans mandate...

public Price(Double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}

//... imagine the getter and setters boilerplate code here ...

@Override
public String toString() {
DecimalFormat fmt = new DecimalFormat("0.00");
return fmt.format(amount) + currency;
}
}
Two simple JavaBeans, a classic example: one models a book, with title, author and price. And the price is composed by the amount and the currency. The Currency class with its hidden partner CurrencyData class are a funny couple in the JDK; if you look at the code you may find it quite amusing; I suggest to never use that. By the way, it is a good example here, because that class other than having such source code, doesn't have a public default constructor, and makes the XML marshaling fail. So it makes a good example for a not so trivial XML serialization.

The following code samples will instantiate a Book object, as per above class definitions, then transform that to an XML String, and after the XML String will be used to reconstruct another instance of the Book object.

JSR57 Serializer. Also known as "XMLDecoder/XMLEncoder"

Let's start with the JSR57 Serializer.

public class Jsr57Spike {
public static void main(String[] args) throws Exception {
Book book = new Book("Carrie", "Stephen King", new Price(17.25,
Currency.getInstance("CHF")));

String xml = encode(book);
System.out.println(xml);

Object o = decode(xml);
System.out.println("decoded object: " + o);
}

private static String encode(Book book) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder(out);

fixCurrency(encoder);

encoder.writeObject(book);
encoder.close();
return out.toString();
}

private static void fixCurrency(XMLEncoder encoder) {
encoder.setPersistenceDelegate(Currency.class,
new PersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance,
Encoder out) {
return new Expression(Currency.class, "getInstance",
new Object[] { oldInstance.toString() });
}
});
}

private static Object decode(String xml)
throws UnsupportedEncodingException {
XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(xml
.getBytes("UTF-8")));
return decoder.readObject();
}
}

As you can see, I had to write a special handler to deal with the java.util.Currency class (lines 25..33), the PersistencyDelegate for the Currency class has to be set on the encoder before usage. If you don't do it, it will not throw any exception, but the decoder will be unable to deserialize the XML as it will fail to instantiate the Currency class. It will not throw any exception, it will just print on System.error some weird stuff like:

java.lang.InstantiationException: java.util.Currency
Continuing ...
java.lang.RuntimeException: failed to evaluate: <unbound>=Class.new();
Continuing ...

And the decoded object will miss the currency instances. You can easily try that commenting the line number 17, where the "fix" is applied.

With the fix in place, everything should go fine and produce the following output:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_20" class="java.beans.XMLDecoder">
<object class="it.newinstance.xml.spike.model.Book">
<void property="author">
<string>Stephen King</string>
</void>
<void property="price">
<object class="it.newinstance.xml.spike.model.Price">
<void property="amount">
<double>17.25</double>
</void>
<void property="currency">
<object class="java.util.Currency" method="getInstance">
<string>CHF</string>
</object>
</void>
</object>
</void>
<void property="title">
<string>Carrie</string>
</void>
</object>
</java>

decoded object: Carrie by Stephen King, 17.25CHF

We may find that the above XML representation is possibly too verbose and too Java-centric. So we may look forward to Jaxb...

But it's nice to notice here that in this case I didn't have to touch the model (Book and Price class) as the XMLDecoder/Encoder are really not intrusive and may be suitable for JavaBeans from which we do not have the source code or we can't easily change.

JAXB: Java Architecture for XML Binding

Following code does again the same stuff: from a JavaBean to XML and the way back.

public class JaxbSpike {
@XmlRootElement
private static class XMLBook extends Book {
@SuppressWarnings("unused")
public XMLBook() {} // default constructor is mandated by JavaBeans spec

public XMLBook(String title, String author, Price price) {
super(title, author, price);
}
}

public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(XMLBook.class);
Book book = new XMLBook("Carrie", "Stephen King", new Price(17.25,
Currency.getInstance("EUR")));

String xml = marshall(jc, book);
System.out.println(xml);

Book unmashalledBook = unmarshall(jc, xml);
System.out.println("Last book I read: " + unmashalledBook);
}

private static Book unmarshall(JAXBContext jc, String xml)
throws JAXBException {
Unmarshaller u = jc.createUnmarshaller();
return (Book) u.unmarshal(new StringReader(xml));
}

private static String marshall(JAXBContext jc, Book book)
throws JAXBException, PropertyException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(book, out);
return out.toString();
}
}

Jaxb requires that the object which represents the XML root has to be annotated with the @XmlRootElement. As I don't like to change my model just to transform it, I preferred in this example to subclass Book and apply the XML serialization to the inner class XMLBook where I can add the annotation without any problem.

At line 34 I set the JAXB_FORMATTED_OUTPUT property to true to produce a formatted XML. This is helpful for human reading, but it is good to have the ability to produce an XML on a single line to optimize the fruition by machines.

The rest of the code is quite self explanatory.

Here, I didn't had to add any handler for the Currency class in the marshaller/unmarshaller code... But I had to declare the converter with an annotation applied to the package. To annotate the package containing the model classes you need to create a special file named "package-info.java" with following source:

@XmlJavaTypeAdapter(value=CurrencyAdapter.class,type=Currency.class)
package it.newinstance.xml.spike.model;
import java.util.Currency;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

The marshaller/unmarshaller will check the annotations applied to the classes, the fields, and the packages, and apply the specified rules. JAXB has a rich set of annotations that can be applied to your JavaBeans and the packages themselves. The problem is that annotations are, under all aspects, interface elements. Annotating a class means introducing dependencies and changing the interfaces of your domain model. Also take in mind that sometime you just don't own the source code of the JavaBeans you want to serialize or you cannot change them. Subclassing may help, as I showed in the above example, introducing the XMLBook inner class, but it may be not enough. Further investigation of the JAXB API may show solutions to this issue, but at the moment, I don't know...

Then I had also to create a very simple CurrencyAdapter class, which will be used by JAXB to handle the translations Java <-> XML

public class CurrencyAdapter extends XmlAdapter<String, Currency>{

@Override
public String marshal(Currency v) throws Exception {
return v.toString();
}

@Override
public Currency unmarshal(String v) throws Exception {
return Currency.getInstance(v);
}
}

If you don't set up the CurrencyAdapter, the Exception that you will get is the following:

Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
  java.util.Currency does not have a no-arg default constructor.
	this problem is related to the following location:
		at java.util.Currency
		at public java.util.Currency it.newinstance.xml.spike.model.Price.getCurrency()
		at it.newinstance.xml.spike.model.Price
		at public it.newinstance.xml.spike.model.Price it.newinstance.xml.spike.model.Book.getPrice()
		at it.newinstance.xml.spike.model.Book
		at it.newinstance.xml.spike.jaxb.JaxbSpike$XMLBook

	at com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:91)
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:436)
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:277)
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1100)
	at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:143)
	at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:110)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
	at javax.xml.bind.ContextFinder.find(ContextFinder.java:376)
	at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
	at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)
	at it.newinstance.xml.spike.jaxb.JaxbSpike.main(JaxbSpike.java:32)

I have to say that the exception is very descriptive of the problem. JAXB, for the little I had the chance to see, is a very well made API.

So, now that we have all in place, let's see what's the output of our JaxbSpike:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xmlBook>
<author>Stephen King</author>
<price>
<amount>17.25</amount>
<currency>EUR</currency>
</price>
<title>Carrie</title>
</xmlBook>

Last book I read: Carrie by Stephen King, 17.25EUR

For sure a better, simpler and non-Java-centric representation of the Book object, if we compare this with the output given using the XMLEncoder.

But, we have to notice that we have introduced the annotation on the package, so we changed the original domain. It is possible that, learning better the JAXB APIs, this change may be avoided, but I am really not confident in that. For sure JAXB is the best choice when you have some flexibility in changing the code of your JavaBeans.

Want to try by yourself?

Here you find the source code: JavaBeansToXml.tar.gz

Good luck!

P.S. This is all I know about JAXB and JSR57 XML serialization; if you have a specific problem it's better to refer to some community forum. But feel free to leave a comment, I will be happy to help if I know the answer.

From http://en.newinstance.it/2010/08/05/javabeans-to-xml-with-no-libraries/

Published at DZone with permission of Luigi Viggiano, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Peter Karussell replied on Mon, 2010/08/16 - 5:24am

Not that I am a word puzzler ;-) but you mean "with no external library", right?

for those who want a simple (but external) solution try simple or xstream (can produce json too) or others.

Luigi Viggiano replied on Mon, 2010/08/16 - 7:10am

> Not that I am a word puzzler ;-) but you mean "with no external library", right?

Exactly.

Dan Howard replied on Fri, 2010/09/17 - 6:51am

xstream has one major failing. It doesn't hanlde object versioning at all.

Chris Hardin replied on Fri, 2010/09/17 - 9:26am

I like it and I think you should bundle it up and make it a library. :)

This falls under "reinventing the wheel" There are plenty of other libraries out there that can handle this task. Adding a dependency and learning the library is a more valuable, marketable skill than writing your own. Better to concentrate on writing your domain specfic code than writing someone that others have already done.

 This was a great learning exercise though...

King Sam replied on Fri, 2012/02/24 - 10:24am

Rather a strange design decision of the Java serializer is the fact that it only saves the changes of an object made to it after instantiation.

This is fine as long as you don’t change the default values of an object. Or even better, do not set default values.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.