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.
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.
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.
Run xml_is_well_formed_document or xml_is_well_formed_content against the same string to confirm whether libxml accepts it. For example:
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.
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:
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.
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.
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.
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.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL