My question is very similar to How to prevent marshalling empty tags in JAXB when string is empty but not null
The difference is that I am unable to add the annotation to package-info.java as all our JAXB types are generated from schemas with every build. I also would much prefer not to change JAXB providers if possible.
What I want to achieve is that setting an empty String will not create the element, but I need to set this for all generated JAXB types from many schemas. Is there a way to apply this to all String fields in all generated JAXB classes?
Update I have managed to get the XML adapter generating for all Strings in the schema by making the following changes:
In the project POM, I added this to the maven-jaxb2-plugin:
<bindingDirectory>src/main/resources</bindingDirectory>
<bindingIncludes>
<include>bindings.xjb</include>
</bindingIncludes>
And here is my bindings.xjb file:
<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1">
<jxb:globalBindings>
<jxb:javaType name="java.lang.String" xmlType="xs:token"
parseMethod="com.project.Formatter.parseString"
printMethod="com.project.Formatter.printString"/>
</jxb:globalBindings>
</jxb:bindings>
And the formatting method:
public static String printString(final String value)
{
if (StringUtils.isBlank(value))
{
return null;
}
return value;
}
The problem is that this causes a Null Pointer Exception deep within JAXB. Here is the stacktrace:
Caused by: java.lang.NullPointerException
at com.sun.xml.bind.v2.runtime.output.SAXOutput.text(SAXOutput.java:158)
at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:321)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:209)
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:250)
at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:98)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:185)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:305)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:312)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:71)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103)
The cause of this problem boils down to this method:
com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.CompositeTransducedAccessorImpl.hasValue(BeanT)
The above method will render the element if the value is not null before any adapter is run.
Is there any way to override the Accessor used in JAXB so that this will run the adapter before determining whether to render the element? Is there another way to achieve what I want?