javaxmljaxbmarshalling

Why does JAXB need a no arg constructor for marshalling?


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?


Solution

  • 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 an XmlAdapter specified via @XmlJavaTypeAdapter at field, property, or package level:


    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
    }