Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB marshalling / unmarshalling NullpointerException dilemma

Tags:

java

xml

jaxb

I am using a java.xml.bind-annotated Bean to create an XML output format that omits collections if they are empty. For that purpose the respective Getter has to look like this:

@XmlElementWrapper(name = "titles")
@XmlElement(name = "title")
public List<XmlTitle> getTitles() {
    if (titles.size() == 0) {
        return null;
    }
    return titles;
}

This works fine during marshaling. Sadly, I am getting a NullpointerException once I want to unmarshal the very same XML file:

java.lang.NullPointerException
    at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:305)
    at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:269)
    at com.sun.xml.bind.v2.runtime.unmarshaller.Scope.add(Scope.java:121)
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(ArrayERProperty.java:213)
    at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(UnmarshallingContext.java:538)
    at com.sun.xml.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.endElement(ValidatingUnmarshaller.java:107)
    at com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:158)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:609)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2973)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
    at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:258)
    at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:229)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
    at org.springframework.oxm.jaxb.Jaxb2Marshaller.unmarshal(Jaxb2Marshaller.java:754)
    at org.springframework.oxm.jaxb.Jaxb2Marshaller.unmarshal(Jaxb2Marshaller.java:735)

This has to do with namely that Getter returning null. As some JAXB expert explains on the Glassfish JAXB forum:

The JAXB unmarshaller does not expect setters for properties of type List; it'll just use x.getSomeList().add( y ) to add another child.

How do I best solve this dilemma?

like image 303
Fritz Duchardt Avatar asked Oct 30 '22 22:10

Fritz Duchardt


2 Answers

As per exception JAXB does not allow lists to be null when marschalling. So instead of using the annotation @XmlElementWrapper(name = "titles") you could create a wrapper class which you use in the containing class to hold the list of XmnlTitle. If you look as xjc generated classes you will find that this is exactly how it handles wrapped list elements.

Also JAXB automatically omits elements which are null, this is how you managed to not unmarschal the list by returning null when size() == 0

Wrapper:

public class XmlTitleWrapper {
    private List<XmlTitle> title;

    public void setTitle(List<XmlTitle> title) {
        this.title = title;
    }

    @XmlElement(name = "title")
    public List<XmlTitle> getTitle() {
        if(title == null) {
            title = new ArrayList<XmlTitle>();
        }
        return title;
    }

    @Override
    public String toString() {
        return "XmlTitleWrapper [title=" + title + "]";
    }
}

Container:

@XmlRootElement
public class Container {

    private XmlTitleWrapper titles;

    @XmlElement(name = "titles")
    public XmlTitleWrapper getTitles() {
        return titles;
    }

    public void setTitles(XmlTitleWrapper titles) {
        this.titles = titles;
    }

    @Override
    public String toString() {
        return "Container [titles=" + titles + "]";
    }
}

Test:

Container c1 = new Container();
List<XmlTitle> title = Arrays.asList(new XmlTitle("A"), new XmlTitle("B"));
XmlTitleWrapper wrapper = new XmlTitleWrapper();
wrapper.setTitle(title);
c1.setTitles(wrapper);
StringWriter writer = new StringWriter();
JaxbUtil.toXML(c1, writer);
System.out.printf("%s%n", String.valueOf(writer));

this will generate:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container>
    <titles>
        <title>
            <value>A</value>
        </title>
        <title>
            <value>B</value>
        </title>
    </titles>
</container>

If you remove the line setting the wrapper

c1.setTitles(wrapper);

thus leaving it null in the container, then the output will be:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container/>
like image 111
A4L Avatar answered Nov 15 '22 05:11

A4L


You could create a XmlAdapter in which you control marshalling and unmarshalling behavior.

like image 30
Amir Keibi Avatar answered Nov 15 '22 06:11

Amir Keibi