XML well-formedness errors and how to fix each one

Twelve XML errors that stop a parser, each with a broken example, the position reported and the fix, plus why formatting must not touch mixed content.

On this page

HTML forgives almost anything: a missing closing tag, an unquoted attribute, a stray ampersand. XML forgives nothing. The specification says that when a parser meets a well-formedness error it must stop and report it, not guess. That strictness is why an XML feed, a sitemap, an invoice file or an Android layout can break on a single character, and why the error message is often the only clue you get.

This article goes through the errors that come up most, each with a minimal broken example, what our XML formatter and validator reports for it, and the fix. Line and column numbers count from 1.

Well-formed versus valid

Well-formed means the text obeys XML's own syntax: one root element, every tag closed and properly nested, attributes quoted, special characters escaped. Every XML parser checks this.

Valid means the document also follows a schema (a DTD, XSD or RelaxNG) that says which elements may appear where. A file can be perfectly well-formed and still invalid, for example a sitemap whose <loc> is missing. The errors below are all well-formedness errors: nothing downstream will even look at your document until they are fixed.

Characters that must be escaped

1. A bare ampersand. <name>Bread & Butter</name> fails at line 1, column 19, the &. In XML, & always starts a reference such as &amp;. Write Bread &amp; Butter. This is the single most common error in hand-edited and generated XML, usually from a product name or a URL with a query string: ?a=1&b=2 must be written ?a=1&amp;b=2 inside XML.

2. HTML entity names. <p>Price&nbsp;10</p> fails at column 9 on &nbsp;. XML predefines only five named entities: &lt;, &gt;, &amp;, &quot; and &apos;. Everything else needs a numeric reference (&#160; for a non-breaking space) or the character itself in UTF-8. The HTML entity encoder converts between the forms; see HTML entities, UTF-8 and escaping for when you need them.

3. A less-than sign in an attribute. <rule test="a < b"/> fails at column 15. A < is never allowed inside an attribute value; write &lt;. A > is allowed, which surprises people the other way.

4. Control characters. <a>bell followed by the character U+0007 fails with "The character U+0007 is not allowed in XML". XML 1.0 forbids most control characters below U+0020 (tab, line feed and carriage return are the exceptions), even when escaped as &#7;. They usually arrive from text pasted out of a terminal or a database field. Remove them before generating the XML.

Structure errors

5. Two root elements. A file containing <item>1</item> and then <item>2</item> on line 2 fails at line 2, column 1: "A second root element starts here." An XML document has exactly one root. Wrap the items: <items>…</items>. This often happens when someone concatenates two XML exports.

6. Mismatched or overlapping tags. <order><line><qty>2</line></qty></order> fails at column 20: "This closing tag is </line>, but the open element is <qty>." Elements must close in the reverse order they opened. HTML parsers silently repair <b><i>x</b></i>; XML parsers refuse.

7. Case. <Item></item> fails the same way. XML names are case-sensitive, so Item and item are different elements.

8. Unquoted attributes. <item id=42/> fails at column 10. Every attribute value needs single or double quotes: id="42".

9. Duplicate attributes. <item id="1" id="2"/> fails on the second id, at column 14. An element may have each attribute once.

Position and declaration errors

10. Anything before the XML declaration. A file that begins with a blank line and then <?xml version="1.0"?> fails at line 2, column 1: the declaration must be the very first thing. The usual culprit is a template that outputs a newline before it, or a PHP file with whitespace before <?php. A byte order mark is the one thing allowed before it, and the tool removes it before checking.

11. Undeclared namespace prefixes. <g:price>10</g:price> fails because the prefix g: is not declared. In a Google Merchant feed the root must carry xmlns:g="http://base.google.com/ns/1.0". A plain XML 1.0 parser without namespaces would accept the name; any namespace-aware consumer will not, which is why the tool checks.

12. Double hyphens in comments. <!-- a -- b --> fails: a comment may not contain -- or end with -. Commenting out a block that already contains a comment, or a command-line flag like --verbose, triggers this.

What a formatter must not change

Once the document parses, pretty-printing looks harmless, but whitespace in XML can be data. Take:

<order id="7"><line sku="A1" qty="2"/><note>Leave at <b>door</b></note></order>

Our formatter turns that into:

<order id="7">
  <line sku="A1" qty="2"/>
  <note>Leave at <b>door</b></note>
</order>

order holds only elements, so new lines and indentation between its children are safe. note holds text mixed with an element (mixed content), so it is written exactly as it was. Re-indenting it would put line breaks and spaces into the text "Leave at door", and some consumers would print them. Attribute order and quote style are kept too, because a person reading a diff expects them to stay put; sorting attributes is an option, off by default.

Common mistakes

  1. Building XML by string concatenation. Use a library that escapes, or escape & < > " ' yourself in every value.
  2. Fixing an error at the reported position when the real cause is earlier. A mismatched tag is reported where the parser noticed, which can be many lines after the tag you forgot to close. Look at the open element named in the message.
  3. Saving the file in a legacy encoding while the declaration says UTF-8. Vietnamese or accented text then breaks the parse or turns into garbage.
  4. Treating "it opens in the browser" as proof that it is correct. A browser checks only that the file is well-formed; a feed reader or an e-invoice portal also checks it against a schema.

Paste any document into the XML formatter: it either formats it or points at the first error with the line, the column and a caret under the character, all in your browser.

Tools mentioned in this article

Back to the blog