JAXB marshals XML differently to OutputStream vs. StringWriter
Solution 1
Looks like this might be a "bug" in JAXB. Looking at the source, the calls for marshal() create different writers based on the output/writer type parameter:
public void marshal(Object obj, OutputStream out, NamespaceContext inscopeNamespace) throws JAXBException {
write(obj, createWriter(out), new StAXPostInitAction(inscopeNamespace,serializer));
}
public void marshal(Object obj, XMLStreamWriter writer) throws JAXBException {
write(obj, XMLStreamWriterOutput.create(writer,context), new StAXPostInitAction(writer,serializer));
}
The implementations of the writers is different with regards to how they handle "empty elements". The above code is from:
jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\MarshallerImpl.java.
The two writers you are creating are:
jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\output\UTF8XmlOutput.java
jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\output\XMLStreamWriterOutput.java
Solution 2
The good news is that JAXB is a specification with more than one implementation (just like JPA). If one implementation is not meeting your needs, others are available such as EclipseLink JAXB (MOXy):
Solution 3
I don't know why JAXB is doing this - or even if it is JAXB - if JAXB is outputting XML via a SAXContentHandler for example, then it has no direct control over how close tags are produced.
To get consistent behaviour, you could wrap your OutputStream in a OutputStreamWriter, e.g.
public void marshal(Marshalable obj, OutputStream os) throws XMLMarshalException {
try {
marshaller.marshal(obj, new OutputStreamWriter(os, "UTF-8"));
} catch (JAXBException jaxbe) {
throw new XMLMarshalException(jaxbe);
}
}
Along the same lines, you might see what happens if you wrap the StringWriter in a PrintWriter. Maybe there is some custom code that detects StringWriter
to tries to keep the output short as possible. Sounds unlikely, but I have no other explanation.
Andy
Groovy, Java, Ruby, encryption, hashing, certificates, TLS, Kerberos & LDAP (though not a huge fan), SQL, and IoT. Committer and PMC member for Apache NiFi.
Updated on May 01, 2020Comments
-
Andy about 4 years
I apologize if this has been answered, but the search terms I have been using (i.e. JAXB @XmlAttribute condensed or JAXB XML marshal to String different results) aren't coming up with anything.
I am using JAXB to un/marshal objects annotated with
@XmlElement
and@XmlAttribute
annotations. I have a formatter class which provides two methods -- one wraps the marshal method and accepts the object to marshal and anOutputStream
, the other just accepts the object and returns the XML output as a String. Unfortunately, these methods do not provide the same output for the same objects. When marshaling to a file, simple object fields internally marked with@XmlAttribute
are printed as:<element value="VALUE"></element>
while when marshaling to a String, they are:
<element value="VALUE"/>
I would prefer the second format for both cases, but I am curious as to how to control the difference, and would settle for them being the same regardless. I even created one static marshaller that both methods use to eliminate different instance values. The formatting code follows:
/** Marker interface for classes which are listed in jaxb.index */ public interface Marshalable {}
/** Local exception class */ public class XMLMarshalException extends BaseException {}
/** Class which un/marshals objects to XML */ public class XmlFormatter { private static Marshaller marshaller = null; private static Unmarshaller unmarshaller = null; static { try { JAXBContext context = JAXBContext.newInstance("path.to.package"); marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); unmarshaller = context.createUnmarshaller(); } catch (JAXBException e) { throw new RuntimeException("There was a problem creating a JAXBContext object for formatting the object to XML."); } } public void marshal(Marshalable obj, OutputStream os) throws XMLMarshalException { try { marshaller.marshal(obj, os); } catch (JAXBException jaxbe) { throw new XMLMarshalException(jaxbe); } } public String marshalToString(Marshalable obj) throws XMLMarshalException { try { StringWriter sw = new StringWriter(); return marshaller.marshal(obj, sw); } catch (JAXBException jaxbe) { throw new XMLMarshalException(jaxbe); } } }
/** Example data */ @XmlType @XmlAccessorType(XmlAccessType.FIELD) public class Data { @XmlAttribute(name = value) private String internalString; }
/** Example POJO */ @XmlType @XmlRootElement(namespace = "project/schema") @XmlAccessorType(XmlAccessType.FIELD) public class Container implements Marshalable { @XmlElement(required = false, nillable = true) private int number; @XmlElement(required = false, nillable = true) private String word; @XmlElement(required = false, nillable = true) private Data data; }
The result of calling
marshal(container, new FileOutputStream("output.xml"))
andmarshalToString(container)
are as follows:Output to file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:container xmlns:ns2="project/schema"> <number>1</number> <word>stackoverflow</word> <data value="This is internal"></data> </ns2:container>
and
Output to String
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:container xmlns:ns2="project/schema"> <number>1</number> <word>stackoverflow</word> <data value="This is internal"/> </ns2:container>
-
Andy almost 14 yearsI ended up doing exactly this before checking back here and got consistent output. Thanks.
-
bdoughan almost 14 yearsMore accurately this is a bug in the Metro JAXB implementation. Other JAXB implementations such as EclipseLink JAXB (MOXy) do not have this bug.
-
Force Gaia over 8 yearsIndeed, as that link is now defunct