Hopefully an easy one for JAXB experts:
I am trying to marshal an immutable class that does not define a default no-arg constructor. I have defined an XmlAdapter
implementation but it doesn't seem to be picked up. I have put together a simple self-contained example, which is still failing to work. Can anyone advise what I'm doing wrong?
Immutable Class
@XmlJavaTypeAdapter(FooAdapter.class)
@XmlRootElement
public class Foo {
private final String name;
private final int age;
public Foo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
Adapter and Value Type
public class FooAdapter extends XmlAdapter<AdaptedFoo, Foo> {
public Foo unmarshal(AdaptedFoo af) throws Exception {
return new Foo(af.getName(), af.getAge());
}
public AdaptedFoo marshal(Foo foo) throws Exception {
return new AdaptedFoo(foo);
}
}
class AdaptedFoo {
private String name;
private int age;
public AdaptedFoo() {}
public AdaptedFoo(Foo foo) {
this.name = foo.getName();
this.age = foo.getAge();
}
@XmlAttribute
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@XmlAttribute
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Marshaller
public class Marshal {
public static void main(String[] args) {
Foo foo = new Foo("Adam", 34);
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Foo.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(foo, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
Stack Trace
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Foo does not have a no-arg default constructor.
this problem is related to the following location:
at Foo
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:451)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:283)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:126)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1142)
at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:130)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:248)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:235)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:445)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:637)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:584)
at Marshal2.main(Marshal2.java:11)
Note that I am using JDK 1.7.0_05.
The following should help:
FOO AS ROOT OBJECT
When
@XmlJavaTypeAdapter
is specified at the type level it only applies to fields/properties referencing that class, and not when an instance of that class is a root object in your XML tree. This means that you will have to convertFoo
toAdaptedFoo
yourself, and create theJAXBContext
onAdaptedFoo
and notFoo
.Marshal
AdaptedFoo
You will need to add an
@XmlRootElement
annotation to theAdaptedFoo
class. You can remove the same annotation from theFoo
class.FOO AS NESTED OBJECT
When
Foo
isn't the root object everything works the way you have it mapped. I have extended your model to demonstrate how this would work.Bar
Demo
Note that the JAXB reference implementation will not let you specify the
Foo
class when bootstrapping theJAXBContext
.input.xml/Output