The XXE fix in Java is to disable DOCTYPE declarations and external entity resolution on every XML parser before it touches untrusted input — the single line factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) closes the attack on the DOM and SAX parsers, and each of Java's XML APIs has an equivalent hardening step. XML External Entity injection is not a bug in your code so much as a dangerous default in the parser: Java's XML APIs historically resolved external entities out of the box, so any XML you parse from an untrusted source can be told to read local files, reach internal network endpoints, or exhaust memory.
The good news is that the XXE fix is mechanical once you know the knob for each parser. The bad news is that Java has at least four XML parser factories, each with its own configuration, and hardening one does nothing for the others.
What XXE actually does
XXE abuses the external entity feature of the XML specification. An attacker submits XML that declares an entity pointing at something they should not be able to read, then references it in the document body. A classic file-disclosure payload looks like this (shown for illustration, not as an exploit against any real target):
<?xml version="1.0"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<foo>&xxe;</foo>
When a naively configured parser processes this, it resolves &xxe; by reading the referenced file and folding its contents into the parsed document — which your application may then log, return, or store. Point the entity at an internal URL instead and you have server-side request forgery. Point it at a recursive entity definition (the "billion laughs" pattern) and you have a denial-of-service through memory exhaustion. All of it flows from the parser being willing to resolve entities it should refuse.
The fix, therefore, is not to sanitize the XML — you cannot reliably filter your way out of this — but to configure the parser so it never resolves external entities in the first place.
The DocumentBuilderFactory fix (DOM parsing)
DocumentBuilderFactory is the most commonly used and the most commonly vulnerable. The strongest single setting is to disallow DOCTYPE declarations entirely, which kills every DOCTYPE-based XXE variant at once:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// The one setting that matters most:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// Belt and braces, in case DOCTYPE cannot be fully disabled in your stack:
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
If your application genuinely never needs DOCTYPEs — which is the common case — the first disallow-doctype-decl line alone is sufficient, and the parser throws an exception the moment a hostile document tries to declare one. That fail-closed behavior is what you want.
The SAXParserFactory and XMLReader fix
SAX-based parsing takes the same features, set on the factory:
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
SAXParser parser = spf.newSAXParser();
If you use XMLReader directly, set the same features on it via reader.setFeature(...). The feature URIs are identical; only the object you call them on changes.
The XMLInputFactory fix (StAX parsing)
StAX uses a different property model. Disable DTD support and external entities like this:
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false);
SUPPORT_DTD = false is the key line — with DTDs off, there is no place for an external entity declaration to live.
The TransformerFactory and SchemaFactory fix
XXE hides in the less obvious XML machinery too. XSLT transformations and schema validation both parse XML and both need hardening. The JDK provides XMLConstants.FEATURE_SECURE_PROCESSING plus two access properties that restrict what external resources a transformer or validator may load:
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
Setting the access properties to an empty string means "no protocols allowed," so no external DTD or stylesheet can be fetched. SchemaFactory takes the analogous ACCESS_EXTERNAL_DTD and ACCESS_EXTERNAL_SCHEMA properties.
Making the XXE fix stick across a codebase
The reason XXE keeps reappearing is that the fix is per-parser and easy to forget on the fifth DocumentBuilderFactory.newInstance() someone adds six months later. Two practices keep it from regressing.
First, centralize parser creation. Wrap the hardened configuration in a single factory method — SafeXml.newDocumentBuilder() — and forbid raw newInstance() calls in review. One correct implementation beats twenty scattered ones that each have to be right.
Second, let tooling watch for the pattern. Static analysis that recognizes XML parser instantiation will flag any factory created without the disallow-doctype/secure-processing settings, so a new unhardened parser fails the build instead of shipping. That is the practical enforcement layer — humans forget, the analyzer does not. The Java security best practices guide covers where this sits among the other input-handling defenses, and the OWASP XXE Prevention Cheat Sheet is the canonical reference for the exact feature strings if you need one authoritative source.
Note that the XXE fix here is about the parsers you configure. Vulnerabilities in XML-processing libraries themselves are a separate track — those you close by keeping the library patched, which is where dependency scanning rather than parser configuration does the work.
FAQ
What is the single most important line for the XXE fix in Java?
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) on your DocumentBuilderFactory or SAXParserFactory. If your application does not need DOCTYPE declarations, this one setting makes the parser reject every DOCTYPE-based XXE payload by throwing an exception.
Does disabling DOCTYPE break legitimate XML?
Only if your application genuinely relies on DOCTYPE declarations or DTD validation, which most modern XML use does not. If you do need DTDs, keep them enabled but turn off external general and parameter entities and disable loading of external DTDs, so internal definitions still work while external resolution is blocked.
Is the XXE fix the same for all Java XML parsers?
The idea is the same — block DOCTYPE and external entities — but the exact API differs. DocumentBuilderFactory and SAXParserFactory use feature strings, XMLInputFactory uses SUPPORT_DTD and the external-entities property, and TransformerFactory/SchemaFactory use secure processing plus the access-external properties. You must harden each one you use.
Can I fix XXE by validating or sanitizing the XML input?
No. You cannot reliably filter external entity declarations out of untrusted XML, and attempting to invites bypasses. The correct fix is to configure the parser so it refuses to resolve external entities at all, which removes the capability rather than trying to police its use.