An XXE example in Java almost always starts the same way: an application parses attacker-supplied XML with a default DocumentBuilderFactory or SAXParserFactory, and those defaults resolve external entities. XML External Entity injection (XXE) abuses that behavior to read local files, reach internal network services, or exhaust resources. The fix is not a library upgrade — it's disabling features the parser enables by default. This post walks through a defensive XXE example, explains what an attacker gains, and gives you copy-paste-safe parser configuration.
What XXE is, conceptually
XML supports entities — named placeholders that get expanded when the document is parsed. Internal entities are harmless string substitutions. The danger is external entities, which tell the parser to go fetch content from a URI and inline it into the document. That URI can be a file:// path or an http:// address to an internal host.
If your application echoes parsed XML back to the user, or uses parsed values in a response, an attacker can define an external entity pointing at a sensitive file and read whatever the application process can read. XXE has been in the OWASP Top 10 for years, and it remains common because so many Java XML APIs are unsafe by default.
A vulnerable XXE example in Java
Here's the pattern that shows up in real code — a service that accepts an XML payload and parses it with stock settings:
// VULNERABLE: default factory resolves external entities
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(userSuppliedXml)));
String result = doc.getDocumentElement().getTextContent();
Now consider the kind of XML an attacker sends. This XXE example defines an external entity that resolves to a local file:
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>
When the vulnerable parser processes this, it resolves &secret; by reading the file and inlining its contents into the data element. If the application returns that text content, the attacker just exfiltrated a server file through your XML endpoint. Swap the URI for http://169.254.169.254/latest/meta-data/ and the same bug becomes a server-side request forgery against a cloud metadata service.
A related variant abuses nested internal entities to cause exponential expansion — the "billion laughs" attack — turning a tiny document into gigabytes of memory usage and taking the service down. Same root cause: the parser does what the document tells it to.
Why the defaults are the problem
The uncomfortable part is that the vulnerable code above looks completely normal. Nobody wrote "fetch this file." The JAXP factories ship with DTD processing and external entity resolution enabled, so a developer who just wants to parse XML inherits the vulnerability without doing anything wrong. This is why XXE is a configuration problem, and why "just don't parse untrusted XML" is unrealistic advice — plenty of legitimate integrations exchange XML.
The fix: harden the parser
The reliable defense is to disable DTDs entirely. If your application never needs a document type definition — and most don't — the single most effective setting rejects any XML that contains one:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// Primary defense: reject any DOCTYPE declaration outright
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// Defense in depth, in case DTDs must be allowed elsewhere
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(userSuppliedXml)));
With disallow-doctype-decl set to true, the malicious payload above is rejected before any entity is resolved, because the parser refuses the <!DOCTYPE ...> line itself. That one feature closes both file disclosure and billion-laughs in a single move.
Apply the same principle to every XML API you use:
- SAXParserFactory and XMLInputFactory (StAX): set the same
disallow-doctype-declfeature, or for StAX setXMLInputFactory.SUPPORT_DTDtofalseandIS_SUPPORTING_EXTERNAL_ENTITIEStofalse. - Transformer / XSLT: set
XMLConstants.ACCESS_EXTERNAL_DTDandACCESS_EXTERNAL_STYLESHEETto the empty string. - Third-party libraries (older Jackson XML, dom4j, JDOM, XMLBeans) have historically shipped unsafe defaults or had XXE-related advisories. Check the version you use.
Detecting XXE before it ships
You catch XXE two ways. Static analysis flags XML parsing that doesn't disable external entities — most SAST tools have a dedicated XXE rule that matches the vulnerable factory pattern above. Dynamic testing sends malicious XML at your endpoints and watches for file contents or out-of-band callbacks in the response; our DAST scanning overview covers how out-of-band detection works.
Don't forget your dependencies. An XML-parsing library buried in your tree can reintroduce XXE even if your own code is hardened. Keeping XML libraries current matters, and a scanner can flag known XXE advisories in components you didn't write. If you're strengthening Java XML handling more broadly, our Jackson databind security guide covers deserialization risks in the same ecosystem.
FAQ
What is the single most important fix for XXE in Java?
Set disallow-doctype-decl to true on your parser factory. It rejects any XML containing a DOCTYPE declaration, which blocks both external-entity file disclosure and the billion-laughs denial-of-service in one step. Only relax it if your application genuinely needs DTDs.
Can XXE do more than read files?
Yes. Beyond reading local files, XXE can drive server-side request forgery by pointing an external entity at internal services or cloud metadata endpoints, and it can cause denial of service through recursive entity expansion. Blind variants exfiltrate data over out-of-band channels even when nothing is echoed back.
Are Java's XML parsers safe by default?
No. The standard JAXP factories enable DTD processing and external entity resolution by default, so ordinary-looking parsing code is vulnerable unless you explicitly disable those features. This is the main reason XXE remains common.
Does upgrading my XML library fix XXE?
Not by itself. Some libraries improved their defaults over time, but XXE is fundamentally a configuration issue in how you construct the parser. Upgrade to patch known library advisories, but you still must disable DTDs and external entities in your own parsing code.