Many people seem to have trouble understanding XmlAdapter/XmlJavaTypeAdapter. I think it's at least partially because of the lack of documentation/samples, but it might be that there's a problem in the design, and if so that's not good.

So today, I'm going to talk about XmlAdapter/XmlJavaTypeAdapter so that people can make more informed discussions about it.

Motivation

Normally, JAXB tries to copy the in-memory representation straight to XML. If your object has fields a, b, and c, your XML will get elements like a, b, and c. This is a reasonable default, but it's also common for in-memory data representation to be vastly different from the on-the-wire representation.

A canonical example of this is a map. On XML, it's usually written like a list:

   Software Engineering ...   Network Security ...  ...  

But in memory, it takes much more complex form that involves arrays, linked lists, and etc. XmlAdapter is a mechanism intended to bridge this gap.

How It Works

XmlAdapter takes two classes as type parameters. One is the class that corresponds to the XML representation. The other is the class that corresponds to the in-memory representation. The former has to be a class that's bindable by JAXB, but the latter can be any Java type. XmlAdapter defines two methods; the unmarshal method is used by unmarshallers to convert the former to the latter, and the marshal method is used by marshallers to convert the latter to the former.

For example, in the above example, suppose in memory you have:

 class Brochure { Map courses; } class Course { String id; String name; } 

Instead of printing the map in its 'raw' form, you'd want to print them like a list, so you write the following adapters:

 class CourseListAdapter extends XmlAdapter> { public Map unmarshal( Course[] value ) { Map r = new HashMap(); for( Course c : value ) r.put(c.id,c); return r; } public Course[] unmarshal( Map value ) { return value.values().toArray(new Course[value.size()]); } } 

And then you annotate your Java class like this:

 @XmlRootElement(name="brochure") class Brochure { @XmlJavaTypeAdapter(CourseListAdapter.class); @XmlElement(name="course") Map courses; } class Course { @XmlAttribute String id; @XmlElement String name; } 

This adapter annotation makes JAXB believe that the Brochure class looks like the following:

 class Brochure { @XmlElement(name="course") Course[] courses; } 

This applies to the unmarshaller, the marshaller, and the schema generator. When you run one of those like

 marshaller.marshal( new Brochure(...), System.out ); 

..., adapters are used behind the scene to maintain this illusion for JAXB. For example, during the unmarshalling, JAXB happily unmarshals Course[], and when it think it set the value to the courses field, an adapter kicks in and it replaces it to Map<String,Course>. Similarly, inside the marshaller, when JAXB wants to get a value from the courses field, an adapter kicks in again and converts the value from a Map to Course[] before JAXB sees the value.

The equivalent of this in Java serialization would be readResolve and writeResolve.

Sharing State Between Application and Adapter

Sometimes it's convenient to maintain application states inside adapters; for example, if you have an adapter that converts string on XML into a java.lang.Class object, you might want to have a ClassLoader in an adapter.

In JAXB, this is done by allowing applications to set configured instances of XmlAdapters to the unmarshaller/marshaller. This is also an opportunity to pass in a sub-class of the declared adapter, if you so wish.

If the application doesn't provide a configured instance, JAXB will create one by calling the default constructor.

Issues

The approach illustrated so far requires that @XmlJavaTypeAdapter to be present on every reference to a map. If you have multiple Maps all over your code, it's often convenient to be able to apply an XmlAdapter globally, like "use CourseListAdapter every Map you'll see in my classes".

If this were for only marshaller and unmarshaller, this can be easily done by, say, adding those adapters to JAXBContext. But for the schema generator to work, it has to be done declaratively, meaning that the schema generator can figure out exactly what adapters apply without running any code.

Another issue is that you can't adapt the root object of the tree, because there's no place to put @XmlJavaTypeAdapter. This is a lesser issue though, because you can always invoke the adapter yourself before passing the value into JAXB.

(I think my blog is getting more and more like articles, which is probably a bad thing)