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?
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/>
You could create a XmlAdapter in which you control marshalling and unmarshalling behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With