PostgreSQL throws 2200L (not_an_xml_document) when a string is well-formed XML but does not conform to CONTENT restrictions in XMLPARSE or xml operations. This typically happens when you attempt to parse an XML fragment or multiple root elements as a document, or when DOCTYPE declarations or processing instructions violate the expected CONTENT vs DOCUMENT distinction.
SQLSTATE 2200L falls under Class 22 (Data Exception) and is distinct from 2200M (invalid_xml_document, which indicates structural malformedness). The 2200L error means the XML is syntactically correct but violates PostgreSQL's CONTENT restrictions. PostgreSQL distinguishes between DOCUMENT (a single root element with optional XML declaration and comments) and CONTENT (fragments, multiple elements, or processing instructions). When you use XMLPARSE(DOCUMENT ...) or cast to xml with xmloption set to DOCUMENT, the parser accepts only well-formed documents. If the string contains multiple top-level elements, a DOCTYPE declaration, or XML processing instructions when DOCUMENT mode is active, libxml2 raises not_an_xml_document. Conversely, if you specify CONTENT mode, these structures are allowed. The key distinction from 2200M is that 2200M signals malformed XML (bad tags, unescaped characters), while 2200L signals well-formed XML that does not match the expected DOCUMENT or CONTENT structure.
Review the failing SQL statement to determine whether it uses XMLPARSE(DOCUMENT ...), an implicit cast to xml (which respects xmloption), or an XML function like XMLTABLE. Look at the actual XML string that triggered the error. Check whether the input contains multiple top-level elements, DOCTYPE declarations, or processing instructions. These are valid XML but not valid DOCUMENT form in PostgreSQL.
Run xml_is_well_formed_content against the same string to confirm whether libxml recognizes it as valid XML (ignoring DOCUMENT restrictions):
SELECT xml_is_well_formed_content('<item>1</item><item>2</item>');
SELECT xml_is_well_formed_content('<?xml version="1.0"?><root/>');If the function returns true, the XML is valid but violates DOCUMENT restrictions. If it returns false, the XML is actually malformed and you should switch to fixing 2200M instead.
If you have a well-formed XML fragment and do not need it to be a strict document, switch from XMLPARSE(DOCUMENT ...) to XMLPARSE(CONTENT ...):
-- Before (fails with 2200L):
SELECT XMLPARSE(DOCUMENT '<item>1</item><item>2</item>');
-- After (succeeds):
SELECT XMLPARSE(CONTENT '<item>1</item><item>2</item>');CONTENT mode allows fragments, multiple root elements, and processing instructions, so 2200L will not be raised.
If you must store XML as a document (e.g., in an xml column with xmloption set to DOCUMENT) but your input is a fragment, wrap the content in a synthetic root using xmlelement or xmlconcat:
SELECT XMLPARSE(DOCUMENT xmlelement(name root, XMLPARSE(CONTENT '<item>1</item><item>2</item>')));
-- Or using xmlconcat:
SELECT XMLPARSE(DOCUMENT xmlconcat(
XMLPARSE(CONTENT '<item>1</item>'),
XMLPARSE(CONTENT '<item>2</item>')
));This creates a single root element that satisfies DOCUMENT requirements while preserving your content.
If your application frequently inserts or casts XML fragments, set the session xmloption to CONTENT before performing the operations:
SET xmloption TO CONTENT;
CREATE TABLE xml_data (
id SERIAL PRIMARY KEY,
payload XML
);
INSERT INTO xml_data (payload) VALUES ('<item>1</item><item>2</item>');This globally allows CONTENT mode for all implicit casts and operations in the session. Use DOCUMENT only when you truly require single-root documents.
If the XML contains a DOCTYPE declaration and you control the source, strip it before passing to PostgreSQL:
-- PostgreSQL XMLPARSE does not accept DOCTYPE
SELECT XMLPARSE(DOCUMENT '<!DOCTYPE root [...] ><root/>');
-- Raises 2200L
-- Remove the DOCTYPE:
SELECT XMLPARSE(DOCUMENT '<root/>');
-- SucceedsIf the DOCTYPE is meaningful, store it separately as plain text and reattach it when exporting XML to clients.
PostgreSQL's XML type is backed by libxml2 and distinguishes between DOCUMENT (strict W3C document form: single root, optional declaration and comments, no DTD in CONTENT mode) and CONTENT (fragments, multiple elements, processing instructions). The default xmloption is DOCUMENT, which affects all implicit casts and functions. Use xml_is_well_formed_document and xml_is_well_formed_content to test payloads before processing. When building XML in application code, prefer xmlelement/xmlconcat/xmlforest rather than string concatenation, as those functions guarantee the output matches your intended structure. For systems migrating XML from databases that allow fragments (e.g., MySQL, SQL Server), wrapping fragments in a synthetic root is a common pattern. The 2200L error is distinct from 2200M (structural malformedness) and 2200N (invalid XML content); understanding which you hit helps diagnose the root cause quickly.
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