Using Interfaces with JAXB

I set about the other day to use JAXB-annotated classes to generate some JSON as part of some web services work.

The trivial case worked.

@XmlRootElement
public class ExtMessage
{
    private String owner;

    @XmlElement
    private ExtConcreteBody body;

}

What I set about doing next caused some immediate grief.  My intention for ‘body’ is to actually be one of many JSON entities. First attempt was to introduce a JSONBody interface and use that instead of ExtConcreteBody.

@XmlRootElement
public class ExtMessage
{
    private String owner;

    @XmlElement
    private JSONBody body;

}

Of course that didn’t work.  At marshalling time, the JAXB provider complained about not supporting interfaces.

A quick search on Google said I wasn’t the only person who had run into this problem before. 

Best resource I found was the JAXB User Guide.  Seems to have some funny rendering and be slightly out of date, but it led me down the correct path.

Essentially you need to make use of an XmlJavaTypeAdapter.  JAXB ships a default one (AnyTypeAdapter) that will generate a ‘type="xs:anyType"’ in your xml schema.  If you want specific type support, you can implement your own Adapter. 

After adding the class-level @XmlJavaTypeAdapter(AnyTypeAdapter.class) to JSONBody, I thought I had it licked.

Then the JAXB provider complained that “Marshalling Error: class […] nor any of its super class is known to this context”.

WTF?

Fortunately that led me to a comment on a random blog post on Collections and JAX-RS mentioning that you should use an @XmlSeeAlso(…) if you want to avoid that error.

Finally it works.

@XmlRootElement

@XmlSeeAlso(ExtConcreteBody.class)
public class ExtMessage
{
    private String owner;

    @XmlElement
    private JSONBody body;

}

@XmlJavaTypeAdapter(AnyTypeAdapter.class)
public interface JSONBody
{
}

@XmlRootElement
public class ExtConreteBody implements JSONBody
{
    private int id;

}

The JSON looked like:

{"extMessage":{"body":{"@type":"extConcreateBody","id":"0"},"owner":"dummy.user"}}

Which seemed reasonable to me.

Hopefully this helps anyone else out there that is having grief trying to do the same thing!