Why does JAXB need a no arg constructor for marshalling?
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 to JAXBContext.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.
public class Customer {
private final String firstName;
private final String lastName;
private Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Object created via builder pattern
public static CustomerBuilder createBuilder() {
...
}
// getters here ...
}
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 with factoryMethod
parameter. Here's how:
@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
...
private static Customer createInstanceJAXB() { // makes JAXB happy, will never be invoked
return null; // ...therefore it doesn't matter what it returns
}
...
}
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.
public class Customer {
...
private Customer() { // makes JAXB happy, will never be invoked
this(null, null); // ...therefore it doesn't matter what it creates
}
...
}
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.
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:
- http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html
java.sql.Date
is not supported by default (although in EclipseLink JAXB (MOXy) it is). This can also be handled using an XmlAdapter
specified via @XmlJavaTypeAdapter
at field, property, or package level:
- http://blog.bdoughan.com/2011/05/jaxb-and-joda-time-dates-and-times.html
- http://blog.bdoughan.com/2011/01/jaxb-and-datetime-properties.html
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?
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 include xsi:nil="true"
.
UPDATE
You could do the following:
SqlDateAdapter
Below is an XmlAdapter
that will convert from the java.sql.Date
that your JAXB implementation doesn't know how to handle to a java.util.Date
which it does:
package forum9268074;
import javax.xml.bind.annotation.adapters.*;
public class SqlDateAdapter extends XmlAdapter<java.util.Date, java.sql.Date> {
@Override
public java.util.Date marshal(java.sql.Date sqlDate) throws Exception {
if(null == sqlDate) {
return null;
}
return new java.util.Date(sqlDate.getTime());
}
@Override
public java.sql.Date unmarshal(java.util.Date utilDate) throws Exception {
if(null == utilDate) {
return null;
}
return new java.sql.Date(utilDate.getTime());
}
}
Foo
The XmlAdapter
is registered via the @XmlJavaTypeAdapter
annotation:
package forum9268074;
import java.sql.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
int i;
@XmlJavaTypeAdapter(SqlDateAdapter.class)
Date d; //java.sql.Date does not have a no-arg constructor
}