How to fix 2200M: invalid_xml_document in PostgreSQL

PostgreSQLINTERMEDIATEMEDIUMUpdated Dec 22, 2025ยท 114 views

PostgreSQL throws 2200M (invalid_xml_document) when libxml2 rejects the payload passed to XMLPARSE, XMLTABLE, XML functions, or an xml column insert because the text is not a well-formed XML document. The parser wants a single root node and complete tag structure, so missing roots, stray text nodes, or bad syntax cause the query to fail before the server ever stores or evaluates the value.

Quick fix

Fixes 2200M

SELECT xml_is_well_formed_document('<books><book/></books>');
SELECT xml_is_well_formed_content('<title>foo</title><author>bar</author>');
  1. Confirm the XML operation and mode
  2. Validate the payload with xml_is_well_formed_* helpers
  3. Repair the XML structure
  4. Choose the correct XML option
postgresql โ€” error
SELECT xml_is_well_formed_document('<books><book/></books>');
SELECT xml_is_well_formed_content('<title>foo</title><author>bar</author>');
2200M: invalid_xml_document

What this error means

SQLSTATE 2200M falls under Class 22 (Data Exception) and is emitted by PostgreSQL whenever an XML value cannot be parsed as a document. The xml type insists on well-formed XML whenever you explicitly request a document value (XMLPARSE(DOCUMENT ...), xmltable/XMLTABLE row inputs, xmlexists/xpath calls that require a document, or inserting into an xml column). If the string being parsed has multiple top-level elements, stray characters outside the root, mismatched tags, or other syntactic problems, libxml2 raises invalid_xml_document and the statement is aborted before any computation is performed. Use the helper functions such as xml_is_well_formed_document and xml_is_well_formed_content to check data ahead of time: those functions understand the Document vs Content distinction that PostgreSQL exposes through SET xmloption. When you supply a well-formed document, the same libxml2 parser that powers XMLPARSE accepts it without 2200M errors.

Typical symptoms

  • ERROR: 2200M: invalid_xml_document when running XMLPARSE(DOCUMENT ...) or casting text to xml.
  • Inserts/updates fail for columns of type xml even though the payload looks like XML.
  • XMLTABLE, xpath, or xmlexists functions raise invalid_xml_document before the XPath expression executes.
  • Logs show the SQLSTATE 2200M along with libxml diagnostics such as "Start tag expected" or "Extra content at the end of the document."
  • Client-side code receives a Postgres error when streaming XML payloads or rehydrating stored xml rows.

Common causes

  1. The payload contains more than one top-level element or text outside a single root node, yet XMLPARSE was invoked with DOCUMENT (the default with xml columns).
  2. A closing tag is missing or mismatched, leaving the document structurally malformed.
  3. Special characters such as <, >, or & appear unescaped outside CDATA sections.
  4. The text was generated in the wrong encoding (or included an encoding declaration) and the characters don't match the server/client encoding, so libxml still sees invalid data.
  5. xmloption is set to DOCUMENT but the splitter produced a fragment, or the application forces CONTENT where a document is required.

How to fix "2200M: invalid_xml_document"

1Confirm the XML operation and mode

Inspect the failing SQL statement or application stack trace so you know which XML function is parsing the text. XMLPARSE(DOCUMENT ...), xmlcolumn inserts, XMLTABLE, xpath, and xmlexists require a fully formed document. If you were already casting with ::xml, remember that PostgreSQL resolves that cast via the session xmloption (DOCUMENT by default). Knowing whether PostgreSQL expects a document or content fragment helps you pick the right remedy.

2Validate the payload with xml_is_well_formed_* helpers

Run xml_is_well_formed_document or xml_is_well_formed_content against the same string to confirm whether libxml accepts it. For example:

sql
SELECT xml_is_well_formed_document('<books><book/></books>');
SELECT xml_is_well_formed_content('<title>foo</title><author>bar</author>');

If the document function returns false, the payload is indeed malformed. Switch to the CONTENT variant if you only have fragments or wrap the fragment in a synthetic root with xmlelement/xmlconcat before parsing.

3Repair the XML structure

Ensure the text has exactly one root element, every opening tag has a matching closing tag, and there are no stray characters or comments outside the root. Escape literal &, <, and > characters (or wrap them in CDATA) and normalize namespaces in an XML editor or code generator. You can also use xmlelement to wrap fragments:

sql
SELECT xmlparse(document xmlelement(name root, xmlconcat(xmlparse(content fragment))));

If you are generating XML in application code, pretty-print it with a library before sending it to PostgreSQL so errors are easier to spot.

4Choose the correct XML option

If your payload is a fragment (multiple siblings, just a text node, etc.) you must either call XMLPARSE(CONTENT ...) / xml_is_well_formed_content or set xmloption TO CONTENT before casting to xml. Conversely, when the server requires DOCUMENT you cannot insert fragments at all. Adjust the xmloption or wrap the fragment in a root element so that PostgreSQL parses the document form that matches your use case.

5Align encodings and remove spurious declarations

In text mode PostgreSQL ignores encoding declarations and assumes client_encoding; if you generated XML in another charset, convert it to the current encoding before sending it. When using binary mode, libxml honors the encoding in the declaration, so ensure it matches the data. If the XML came from a third-party system, consider stripping or fixing the <?xml version="1.0" encoding="..."> line before parsing to avoid mismatched characters triggering 2200M.

Advanced notes

The xml type is backed by libxml2 and requires PostgreSQL to be built with --with-libxml; it always enforces well-formedness. Document vs content is controlled through SET xmloption TO DOCUMENT|CONTENT and the choice affects how casting, XMLPARSE, and xmlexists behave. Use xml_is_well_formed_document/xml_is_well_formed_content to test payloads, and prefer xmlelement/xmlconcat wrappers when you assemble XML programmatically so you can guarantee a single root. Programmers migrating XML from other systems often hit 2200M because other databases tolerate fragments; PostgreSQL does not.

Frequently asked questions

What does "2200M" mean?
SQLSTATE 2200M falls under Class 22 (Data Exception) and is emitted by PostgreSQL whenever an XML value cannot be parsed as a document. ), xmltable/XMLTABLE row inputs, xmlexists/xpath calls that require a document, or inserting into an xml column). If the string being parsed has multiple top-level elements, stray characters outside the root, mismatched tags, or other syntactic problems, libxml2 raises invalid_xml_document and the statement is aborted before any computation is performed.
How do I fix "2200M"?
PostgreSQL throws 2200M (invalid_xml_document) when libxml2 rejects the payload passed to XMLPARSE, XMLTABLE, XML functions, or an xml column insert because the text is not a well-formed XML document. The parser wants a single root node and complete tag structure, so missing roots, stray text nodes, or bad syntax cause the query to fail before the server ever stores or evaluates the value.
What causes "2200M"?
The payload contains more than one top-level element or text outside a single root node, yet XMLPARSE was invoked with DOCUMENT (the default with xml columns). A closing tag is missing or mismatched, leaving the document structurally malformed. Special characters such as <, >, or & appear unescaped outside CDATA sections.
Is the "2200M" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading