DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. JavaBeans to XML, With No Libraries

JavaBeans to XML, With No Libraries

Luigi Viggiano user avatar by
Luigi Viggiano
·
Aug. 16, 10 · Interview
Like (0)
Save
Tweet
Share
15.32K Views

Join the DZone community and get the full member experience.

Join For Free

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/

XML Library code style

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 11 Observability Tools You Should Know
  • Container Security: Don't Let Your Guard Down
  • Steel Threads Are a Technique That Will Make You a Better Engineer
  • Spring Boot, Quarkus, or Micronaut?

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: