Posted by kohsuke
on April 17, 2006 at 4:00 PM PDT
One thing people often get confused about JAXB is with respect to how it handles classes that derive from your base class. Today, I'm going to shed some light on it.
People often get confused about why their sub-classes are not used by JAXB when they read an XML document that uses @xsi:type into Java objects. This question was asked in the forum (I don't think this is the first time but I can't find a reference.)
The first thing you should do, and this applies not only to this issue but all the other unmarshalling related issues, is to register a ValidationEventHandler . Whenever JAXB finds an error in a document, it reports a problem to this interface, then it will try to recover from the error. The default ValidationEventHandler is the one that just ignores all the errors, and while this is useful when you just want to read your XML, this makes the trouble-shooting difficult.
If you implement this interface and prints out validation fields of ValidationEvent (or just use a debugger to sniff around), you will see what "problems" JAXB is hitting.
Now, let's get back to why we are having this problem.
When JAXB unmarshals a document, it does this with a set of Java classes that are known to JAXBContext. Such a set of classes are statically known to a JAXBContext when it's created. This is rather different from Java serialization, where it just "finds" a class dynamically as you de-serialize a stream. Java serialization can do this because a class name is included in the object stream.
In XML, all you have is a namespace URI and a local name pair that identifies a Java class indirectly. The only way for JAXB to know what Java class to instanciate is by knowing in advance what XML type names map to what Java classes. So that's why JAXB can't just locate the "right" class.
So, when you hit a problem where the JAXB doesn't use the right class for @xsi:type, it almost always mean that the JAXBContext does not include the right set of classes. An error message from JAXB runtime should verify this, as it will say something like "I found an XML type 'foobar' but I don't know any Java class that matches it".
JAXBContext can be created in two slightly different ways, although underneath they do pretty much the same.
- You can give it a list of java.lang.Classes. JAXB then analyzes those classes, and if those classes statically refers to other classes, they will be also added to "the set". This process will be repeated until all statically reachable classes are accounted for.
- You can give it a set of package names. JAXBContext locates ObjectFactory classes for those packages, and then do the same process outlined above. Since ObjectFactory is a factory class and it tends to have a reference to all the classes in that package in the form of factory methods (if it's generated by XJC), this effectively adds all the classes in a package in one-shot.
This "statically reachable" portion deserves more explanation. If Foo has a field whose type is Bar, this is statically reachable, because a program looking at Foo can discover Bar through reflection. Unfortunately, inheritance is not statically reachable --- if Zot extends Foo, a program that looks at Foo cannot find Zot class, because you can't list such classes. This is why JAXB fails to locate your sub-classes. Because Java doesn't let us do so.
Therefore, to fix this problem, you only need to change the way JAXBContext is created. The simplest way, if you are using XJC, is to create a JAXBContext from a list of packages. Otherwise, you need to list sub-classes explicitly to JAXBContext.newInstance invocation, like this:
If your use of JAXB is substantial and you can't list those class names manually, maybe you can write a little annotation processor that generates a list of class names (if you are interested in writing it, let's talk, so that we can host it in the jaxb project for others to benefit!)