If you try to marshal a class which references a complex type that does not have a no-arg constructor, such as:
import java.sql.Date;
@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
int i;
Date d; //java.sql.Date does not have a no-arg constructor
}
with the JAXB implementation that is part of Java, as follows:
Foo foo = new Foo();
JAXBContext jc = JAXBContext.newInstance(Foo.class);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(foo, baos);
JAXB will throw a
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions java.sql.Date does not have a no-arg default constructor
Now, I understand why JAXB needs a no-arg constructor on unmarshalling - because it needs to instantiate the object. But why does JAXB need a no-arg constructor while marshalling?
Also, another nit, why does Java's JAXB implementation throw an exception if the field is null, and isn't going to be marshalled anyway?
Am I missing something or are these just bad implementation choices in Java's JAXB implementation?
When a JAXB (JSR-222) implementation initializes its metadata it ensures that it can support both marshalling and unmarshalling.
For POJO classes that do not have a no-arg constructor you can use a type level
XmlAdapter
to handle it:java.sql.Date
is not supported by default (although in EclipseLink JAXB (MOXy) it is). This can also be handled using anXmlAdapter
specified via@XmlJavaTypeAdapter
at field, property, or package level:What exception are you seeing? Normally when a field is null it is not included in the XML result, unless it is annotated with
@XmlElement(nillable=true)
in which case the element will includexsi:nil="true"
.UPDATE
You could do the following:
SqlDateAdapter
Below is an
XmlAdapter
that will convert from thejava.sql.Date
that your JAXB implementation doesn't know how to handle to ajava.util.Date
which it does:Foo
The
XmlAdapter
is registered via the@XmlJavaTypeAdapter
annotation:Some enterprise and Dependency Injection frameworks use reflection Class.newInstance() to create a new instance of your classes. This method requires a public no-arg constructor to be able to instantiate the object.
You seem to be under the impression that the JAXB introspection code will have action specific paths for initialization. if so, that would result in a lot of duplicate code and would be a poor implementation. i would imagine that the JAXB code has a common routine which examines a model class the first time it is needed and validates that it follows all the necessary conventions. in this situation, it is failing because one of the members does not have the required no-arg constructor. the initialization logic is most likely not marshall/unmarshall specific and also highly unlikely to take the current object instance into account.
To answer your question: I think this is just poor design in JAXB (or perhaps in JAXB implementations). The existence of a no-arg constructor gets validated during creation of the
JAXBContext
and therefore applies regardless if you want to use JAXB for marshalling or unmarshalling. It would have been great if JAXB would defer this type of check toJAXBContext.createUnmarshaller()
. I think it would be interesting to dig into if this design is actually mandated by the spec or if it is an implementation design in JAXB-RI.But there's indeed a workaround.
JAXB doesn't actually need a no-arg constructor for marshalling. In the following I'll assume you are using JAXB solely for marshalling, not unmarshalling. I also assume that you have control over the immutable object which you want to marshall so that you can change it. If this is not the case then the only way forward is
XmlAdapter
as described in other answers.Suppose you have a class,
Customer
, which is an immutable object. Instantiation is via Builder Pattern or static methods.True, that by default you cannot get JAXB to unmarshall such an object. You'll get error "....Customer does not have a no-arg default constructor".
There are at least two ways of solving this. They both rely on putting in a method or constructor solely to make JAXB's introspection happy.
Solution 1
In this method we tell JAXB that there's a static factory method it can use to instantiate an instance of the class. We know, but JAXB doesn't, that indeed this will never be used. The trick is the
@XmlType
annotation withfactoryMethod
parameter. Here's how:It doesn't matter if the method is private as in the example. JAXB will still accept it. Your IDE will flag the method as unused if you make it private, but I still prefer private.
Solution 2
In this solution we add a private no-arg constructor which just passes null into the real constructor.
It doesn't matter if the constructor is private as in the example. JAXB will still accept it.
Summary
Both solutions satisfies JAXB's desire for no-arg instantiation. It is a shame that we need to do this, when we know by ourselves that we only need to marshal, not unmarshal.
I have to admit that I do not know to what extent this is a hack that will only work with JAXB-RI and not for example with EclipseLink MOXy. It definitely works with JAXB-RI.