To convert a String to an XML Document in Java, we wrap the String in a StringReader and an InputSource, and pass it to DocumentBuilder.parse(), which returns an org.w3c.dom.Document. The classes are part of the JDK, in javax.xml.parsers and org.xml.sax, so no library is needed.
We parse XML from a String when the XML arrives as text rather than as a file, such as the body of a REST or SOAP call, a message from a queue, a column in a database table or a test fixture. Once we have a Document, we can read it with the DOM methods or XPath, change it and write it out again.
The following example parses a short weather forecast held in a text block.
String xml = """
<forecast city="Pune">
<day date="2026-10-11" high="31" low="22"/>
<day date="2026-10-12" high="29" low="21"/>
</forecast>""";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
String root = doc.getDocumentElement().getTagName(); // "forecast"
String city = doc.getDocumentElement().getAttribute("city"); // "Pune"
int days = doc.getElementsByTagName("day").getLength(); // 2
Notice the disallow-doctype-decl feature, which makes the parser reject any DOCTYPE declaration and with it the external entities that attackers hide in XML input. We look at each class in the chain, build a reusable parse method, handle malformed input and encodings, and compare the DOM with the alternatives for turning text into Java objects.
1. Classes That Convert a String to an XML Document
A DocumentBuilder reads XML from a source and builds the DOM tree, which is a tree of Node objects with the Document at the top. It has no parse(String xml) method for XML content, so a String needs two small adapters before the parser can read it.

| Class | Package | Role in the conversion |
|---|---|---|
| DocumentBuilderFactory | javax.xml.parsers | Holds the parser settings, such as namespace awareness and security features, and creates builders |
| DocumentBuilder | javax.xml.parsers | Parses a source and returns a Document |
| StringReader | java.io | Turns the String into a character stream |
| InputSource | org.xml.sax | Wraps the stream so the parser accepts it, and can carry a system id for error messages |
| Document | org.w3c.dom | The parsed tree, with getDocumentElement() as the entry point |
The method parse(String) exists, but its argument is a URI, not XML content. Passing XML text to it is a common mistake, and the parser tries to open the text as an address.
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document wrong = builder.parse("<forecast city=\"Pune\"/>"); // MalformedURLException: no protocol
2. A Reusable and Secure Parse Method
In a real app, the conversion goes into one method that every caller uses. That method is the right place for the security settings, because XML from outside the app can contain a DOCTYPE with external entities. When the parser expands such an entity, it reads a local file or calls a URL and puts the result into the document. The attack is called XML External Entity (XXE) injection, and on JDK 25 the default DocumentBuilderFactory still expands external entities from files.
static Document toDocument(String xml) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new DefaultHandler());
return builder.parse(new InputSource(new StringReader(xml)));
}
The method also turns on namespace awareness, which XPath queries with prefixes and getElementsByTagNameNS() need, and it replaces the default error handler. Without that handler, the JDK parser prints a [Fatal Error] line to System.err before it throws, which clutters the logs of a server. DefaultHandler rethrows fatal errors and prints nothing.
String withDoctype = "<!DOCTYPE forecast [<!ENTITY c \"Pune\">]><forecast city=\"&c;\"/>";
Document blocked = toDocument(withDoctype); // SAXParseException: DOCTYPE is disallowed
Say a logistics company accepts shipment notices as XML over HTTP, and a REST endpoint passes the request body to toDocument(). Legitimate partners never send a DOCTYPE, so rejecting it costs nothing, and a crafted request that tries to read /etc/passwd fails before any entity is resolved. Every parser that reads XML from outside the app gets disallow-doctype-decl set to true. On Java 22 and later, factory.setAttribute(“jdk.xml.dtd.support”, “deny”) does the same through a JDK property.
3. Reading Values From the Parsed Document
The entry point of the tree is getDocumentElement(), which returns the root element. Some older examples use getFirstChild() instead, and that breaks as soon as the XML starts with a comment or a processing instruction, because those are children of the Document too.
Document doc = toDocument("<!-- generated --><forecast city=\"Pune\"/>");
String firstChild = doc.getFirstChild().getNodeName(); // "#comment"
String rootElement = doc.getDocumentElement().getNodeName(); // "forecast"
From the root, we read attributes with getAttribute() and find elements with getElementsByTagName(). The next example turns the forecast into Java records, which is what the rest of an app wants to work with.
record Day(LocalDate date, int high, int low) {}
Document doc = toDocument("""
<forecast city="Pune">
<day date="2026-10-11" high="31" low="22"/>
<day date="2026-10-12" high="29" low="21"/>
</forecast>""");
List<Day> forecast = new ArrayList<>();
NodeList dayNodes = doc.getElementsByTagName("day");
for (int i = 0; i < dayNodes.getLength(); i++) {
Element day = (Element) dayNodes.item(i);
forecast.add(new Day(LocalDate.parse(day.getAttribute("date")),
Integer.parseInt(day.getAttribute("high")),
Integer.parseInt(day.getAttribute("low"))));
}
Day first = forecast.get(0); // Day[date=2026-10-11, high=31, low=22]
For deeper documents, XPath is shorter than nested loops, and the article on getting attribute values with XPath uses the same kind of Document. The DOM parser tutorial covers text nodes, whitespace and walking the whole tree.
4. Handling Malformed XML Strings
A String that is not well-formed XML makes parse() throw a SAXParseException. The exception carries the line and column of the problem, which is what we log or return to the caller, because “invalid XML” alone does not help anyone fix the sender.
String broken = "<forecast><day></forecast>";
String problem;
try {
toDocument(broken);
problem = "none";
} catch (SAXParseException e) {
problem = e.getLineNumber() + ":" + e.getColumnNumber() + " " + e.getMessage();
}
String report = problem; // "1:18 The element type "day" must be terminated by the matching end-tag "</day>"."
Two messages come from text before the root element rather than from broken tags. Both are common when XML is cut out of a log file, read with the wrong charset or built by string concatenation.
| Input starts with | Message | Fix |
|---|---|---|
| Any text, such as x<forecast/> | Content is not allowed in prolog. | Remove the text before the first < |
| A byte order mark (BOM) character \uFEFF | Content is not allowed in prolog. | Strip the BOM, or parse the original bytes so the parser handles it |
| Whitespace before <?xml …?> | The processing instruction target matching “[xX][mM][lL]” is not allowed. | Call strip() on the String, since the declaration must come first |
String padded = " <?xml version=\"1.0\"?><forecast city=\"Pune\"/>";
Document fails = toDocument(padded); // SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed.
Document works = toDocument(padded.strip());
String city = works.getDocumentElement().getAttribute("city"); // "Pune"
For a quick validity check, such as a form field that accepts XML, we wrap toDocument() in a method that returns false on SAXException. A well-formed document can still be wrong for our app, and checking it against an XSD is the next step for that.
5. Parsing Bytes Instead of a String
A String in Java is already decoded text, so the encoding attribute in its XML declaration has no effect when we parse through a StringReader. When the XML arrives as bytes, from a file, a socket or an HTTP body, we pass the bytes to the parser as an InputStream. The parser reads the declaration and decodes the bytes with the encoding it names.
byte[] latin1 = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><city>Z\u00fcrich</city>"
.getBytes(StandardCharsets.ISO_8859_1);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
String fromBytes = builder.parse(new ByteArrayInputStream(latin1)).getDocumentElement().getTextContent();
boolean correct = fromBytes.equals("Z\u00fcrich"); // true
String decodedWrong = new String(latin1, StandardCharsets.UTF_8);
String fromString = builder.parse(new InputSource(new StringReader(decodedWrong))).getDocumentElement().getTextContent();
boolean broken = fromString.equals("Z\u00fcrich"); // false
The second attempt decodes the Latin-1 bytes as UTF-8 before parsing, so the letter \u00fc turns into a replacement character, and the declaration cannot repair it later. The rule is to convert bytes to a String only when the XML is text in our app, and otherwise to let the parser decode it.
The method parse() has overloads for other sources too, so bytes, files and streams go in without a String in between.
| XML comes from | Call |
|---|---|
| A String | builder.parse(new InputSource(new StringReader(xml))) |
| A byte array or HTTP body | builder.parse(new ByteArrayInputStream(bytes)) |
| A file | builder.parse(path.toFile()) |
| A classpath resource | builder.parse(getClass().getResourceAsStream(“/forecast.xml”)) |
| A URL | builder.parse(“https://…”), after checking that the URL is trusted |
6. Reusing Factories and Builders
Creating a DocumentBuilderFactory looks up the implementation class, which costs more than the parse of a small String. An app that converts thousands of messages per minute, such as a queue consumer, keeps one configured factory and creates builders from it. Neither DocumentBuilderFactory nor DocumentBuilder is guaranteed to be thread-safe, so a builder belongs to one thread at a time, and reset() restores it to its original settings between documents.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
ThreadLocal<DocumentBuilder> builders = ThreadLocal.withInitial(() -> {
try {
return factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new IllegalStateException(e);
}
});
DocumentBuilder builder = builders.get();
builder.reset();
Document doc = builder.parse(new InputSource(new StringReader("<forecast city=\"Oslo\"/>")));
String city = doc.getDocumentElement().getAttribute("city"); // "Oslo"
The factory is configured once, before any thread uses it, and each thread gets its own builder from the ThreadLocal. For most apps, a new factory and builder per call, as in toDocument(), is fast enough and simpler.
7. DOM Compared With Other Ways to Read an XML String
A DOM Document is the right result when we need to search, change or re-serialize the XML. When we only want the data in Java objects, or the String is very large, other APIs fit better.
| API | Result | Good for |
|---|---|---|
| DOM (DocumentBuilder) | Document tree in memory | Searching with XPath, changing and writing XML back |
| JAXB (Jakarta XML Binding) | Annotated Java objects | Fixed schemas mapped to classes |
| Jackson XML | POJOs or records | Apps that already use Jackson for JSON |
| JDOM2 | JDOM Document with Java collections | A friendlier tree API than DOM |
| StAX | Stream of events | Large documents that do not fit in memory |
8. String to XML Document FAQs
Most surprises with XML strings happen in the steps right before and right after the conversion.
8.1. How Do I Convert the Document Back to a String?
We use a Transformer with a DOMSource and a StreamResult over a StringWriter. The article on converting an XML Document to a String shows indentation, the XML declaration and writing to a file.
8.2. Do I Need setNamespaceAware(true) to Parse a String?
Only when the XML uses namespaces and we query it by namespace. The factory is not namespace-aware by default, and the parse works either way, but XPath with prefixes and getElementsByTagNameNS() find nothing in a document parsed without it, as the article on XPath namespace resolution explains.
8.3. Why Does My Parsed Document Show Up as [#document: null]?
That is the toString() output of the DOM implementation, not an empty document. The Document node has no value, so it prints null. We check the content with getDocumentElement().getTagName() or serialize the document to see the XML.
8.4. Can DocumentBuilder Parse an HTML String?
Only if the HTML is well-formed XHTML. Ordinary HTML has unclosed tags such as <br>, which make the XML parser throw a SAXParseException. For HTML, we use an HTML parser such as jsoup.
9. Conclusion
Converting a String to an XML Document in Java takes one chain of calls, builder.parse(new InputSource(new StringReader(xml))). The factory behind the builder decides how safe and how useful the result is, so it gets disallow-doctype-decl for any input from outside the app and namespace awareness when the XML uses namespaces.
A malformed String throws a SAXParseException with a line and column, text before the root element causes the prolog errors, and bytes belong in an InputStream so the parser can honor the declared encoding. With the Document in hand, getDocumentElement() is the starting point for reading values.
10. References
- DocumentBuilder Javadoc (Java 25)
- DocumentBuilderFactory Javadoc (Java 25)
- InputSource Javadoc
- XML 1.0, Prolog and Document Type Declaration
- OWASP XML External Entity Prevention Cheat Sheet
Happy Learning !!
I’m getting FileNotFoundException in the below step
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
how to write xpath expression for xml which has name space for this document builder