An identity Transformer serializes a DOM tree. The caller must control external stylesheet access and the number of bytes written to its output sink.
Java XML Transformer: bound serialized output and external access
Operational contract
The factory denies external DTD and stylesheet access and enables secure processing. The method writes into a bounded OutputStream that rejects the byte which would exceed 47,000 output bytes. A DOM can produce more serialized bytes than its original input due to escaping and formatting; input caps alone are not output caps. This code assumes the Document came from a trusted parse boundary. It returns a complete byte array only after transformation succeeds. It does not publish the bytes to a final file or sign them.
Failure case
A receipt editor changes one field in a parsed manifest and serializes it for review. If escaped text grows the output past 47,000 bytes, the transform fails and no truncated result is returned. The caller can then revise the business limit or reject the edit; it cannot silently store the prefix.
Java code
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.XMLConstants;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
public class BoundedManifestXmlWriter {
public static byte[] serialize(Document manifest) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
OutputStream capped = new OutputStream() {
private int written;
@Override public void write(int value) throws IOException {
if (written == 47_000) throw new IOException("XML output exceeds cap");
bytes.write(value);
written++;
}
@Override public void write(byte[] data, int offset, int length) throws IOException {
if (length > 47_000 - written) throw new IOException("XML output exceeds cap");
bytes.write(data, offset, length);
written += length;
}
};
factory.newTransformer().transform(new DOMSource(manifest), new StreamResult(capped));
return bytes.toByteArray();
}
}Performance and ownership cost
Serialization is O(B) over emitted bytes and retains O(B) output memory, capped at 47,000, plus the already-held DOM. The byte array copy on return briefly uses another O(B) allocation. A streaming file sink would reduce retained output but needs staging and cleanup.
Common Mistakes
- Do not treat a bounded input as proof of bounded serialized output.
- Do not expose a partial byte array after transformation failure.
- Do not leave external stylesheet access to a provider default.
Connected lessons
- Java DOM parsing: deny external XML access at the factory
- Java XML Schema validation: use a trusted schema without external fetches
- Java Files.move: atomic publication is a filesystem contract
- Java StAX: pull XML events under a count and entity policy
- Java XPath: compile a fixed selection, not user-supplied code
- Java XML namespaces: select by URI and local name
- Java XML and archive boundaries quiz
- Advanced Java
