Question

I am having an issue with using inheritance and JAXB unmarshalling. I have read the number of examples (specifically a much-reference blog at http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html and a very similar SO question here: JAXB xsi:type subclass unmarshalling not working) and am still having difficulties.

Like many of these other questions, I am trying to create an XML representation of an object whose fields rely on subclasses for information. I do not know at compile time what the concrete subclass implementation will be, so things like XmlSeeAlso is not really available.

In my test case I have a Root class with an abstract class (A) that has a concrete subtype (B):

@XmlRootElement
@ToString
    public class Root {

    private A theClass;

    public A getTheClass() {
        return theClass;
    }

    public void setTheClass(A theClass) {
        this.theClass = theClass;
    }
}

@ToString
public abstract class A {

}

@XmlRootElement
@ToString
public class B extends A {
    private  String b = "from B-" + System.currentTimeMillis()
    public String getB() {
       return b;
    }

    public void setB(String b) {
         this.b = b;
    }
}

Where @ToString are annotations from project Lombok.

I can marshall with no problem:

@Test
public void marshallTest() {

    try {
        Root r = new Root();
        B b = new B();

        r.setTheClass(b);
        Class<?>[] classArray = new Class<?> [] { Root.class, B.class };

        JAXBContext context = JAXBContext.newInstance(classArray);
        Marshaller marshaller = context.createMarshaller();

        try (FileWriter fw = new FileWriter(JAXB_TEST_XML)) {
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(r, fw);
        }

        try(StringWriter sw = new StringWriter() ) {
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(r, sw);
            log.debug("{}", sw.toString());
        }

    } catch (IOException | JAXBException e) {
        e.printStackTrace();
        fail(e.getMessage());
    }
}

Which produces the following xml:

<root>
   <theClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b">
      <b>from B-1375211003868</b>
   </theClass>
</root>

When I try to unmarshall (using MOXY JAXB implemntation) I get:

This class does not define a public default constructor, or the constructor raised an exception.
Internal Exception: java.lang.InstantiationException
Descriptor: XMLDescriptor(xml.A --> [])

With the following code:

@Test
public void unmarshall() {

    try {
        JAXBContext context = JAXBContext.newInstance(new Class<?>[] {Root.class, A.class});
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder db = documentBuilderFactory.newDocumentBuilder();

        Root r = null;
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(JAXB_TEST_XML))) {
            Document doc = db.parse(bis);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            JAXBElement<?> result = unmarshaller.unmarshal(doc, Root.class);
            r = (Root) result.getValue();
        }

        assertTrue(r != null & r.getTheClass() != null && r.getTheClass() instanceof B);
    } catch (Exception e) {
        e.printStackTrace();
        fail(e.getMessage());
    }
}

I have tried making the unmarshalling namespace aware (as with JAXB xsi:type subclass unmarshalling not working which has not worked. I have tried using XmlElementRef which does not work either. I have tried the latest glassfish api and implementations downloaded from maven central (2.2.8). I have tried the MOXY eclipse persistence JAXB implementation. Neither have worked. I have tried unmarshalling without using a document builder which does not work as well. I have tried passing the Root.class and A.class into the JAXB context which also does not work.

I have a feeling that I have a fundamental misconception about what is going on. Any hints or ideas would be appreciated. Thank you.

  • Chooks
Was it helpful?

Solution

UPDATE 2

You could use a library to determine the subclasses dynamically and pass that result to MOXy to build the JAXBContext. Below is an enhancement request that was entered suggesting Jandex for this purpose.


UPDATE 1

There was an issue prior to EclipseLink 1.2.0 (released October 23, 2009) where that exception could be thrown even though everything was setup correctly.


You just need to make the JAXBContext aware of the B class. One way to do this is to leverage the @XmlSeeAlso annotation on the A class.

import javax.xml.bind.annotation.XmlSeeAlso;

@XmlSeeAlso({B.class})
public abstract class A {

}

You can also include B in the classes used to bootstrap the JAXBContext:

JAXBContext jc = JAXBContext.newInstance(Root.class, B.class);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top