ExpatError / ParseError: no element found: line 1, column 0 (tested)

xml.parsers.expat.ExpatError: no element found: line 1, column 0 (or xml.etree.ElementTree.ParseError with the same text) means the XML ended before a complete root element. We reproduced every common cause and found the position is the key clue: line 1, column 0 means the parser received zero bytes -- usually an empty file or response, or a file object that was already read to the end -- while a later position means the document was truncated or had no root element.

1. Every common cause, measured

import io, os, tempfile
import xml.etree.ElementTree as ET
import xml.dom.minidom, xml.parsers.expat

def try_parse(label, fn):
    try:
        fn()
        print(f"{label:38} OK")
    except Exception as e:
        print(f"{label:38} {type(e).__module__}.{type(e).__name__}: {e}")

path = os.path.join(tempfile.mkdtemp(), "data.xml")
open(path, "w").close()                                  # empty file
try_parse("ET.parse(empty file)", lambda: ET.parse(path))
try_parse("minidom.parse(empty file)", lambda: xml.dom.minidom.parse(path))
try_parse("ET.fromstring('')", lambda: ET.fromstring(""))
try_parse("ET.fromstring(whitespace only)", lambda: ET.fromstring("  \n "))
try_parse("minidom.parseString('')", lambda: xml.dom.minidom.parseString(""))
try_parse("ET.fromstring(declaration only)", lambda: ET.fromstring('<?xml version="1.0"?>\n'))
try_parse("ET.fromstring(comment only)", lambda: ET.fromstring("<!-- nothing -->"))

with open(path, "w") as f:
    f.write("<root><item>1</item></root>")
with open(path) as f:
    f.read()                                             # something already read it
    try_parse("ET.parse(file already read)", lambda: ET.parse(f))
    f.seek(0)
    try_parse("ET.parse(after f.seek(0))", lambda: ET.parse(f))

buf = io.StringIO()
buf.write("<root/>")                                     # written, not rewound
try_parse("ET.parse(StringIO, no seek)", lambda: ET.parse(buf))
buf.seek(0)
try_parse("ET.parse(StringIO, after seek(0))", lambda: ET.parse(buf))
try_parse("ET.fromstring(truncated)", lambda: ET.fromstring("<root><item>1</item>"))
ET.parse(empty file)                   xml.etree.ElementTree.ParseError: no element found: line 1, column 0
minidom.parse(empty file)              xml.parsers.expat.ExpatError: no element found: line 1, column 0
ET.fromstring('')                      xml.etree.ElementTree.ParseError: no element found: line 1, column 0
ET.fromstring(whitespace only)         xml.etree.ElementTree.ParseError: no element found: line 2, column 1
minidom.parseString('')                xml.parsers.expat.ExpatError: no element found: line 1, column 0
ET.fromstring(declaration only)        xml.etree.ElementTree.ParseError: no element found: line 2, column 0
ET.fromstring(comment only)            xml.etree.ElementTree.ParseError: no element found: line 1, column 16
ET.parse(file already read)            xml.etree.ElementTree.ParseError: no element found: line 1, column 0
ET.parse(after f.seek(0))              OK
ET.parse(StringIO, no seek)            xml.etree.ElementTree.ParseError: no element found: line 1, column 0
ET.parse(StringIO, after seek(0))      OK
ET.fromstring(truncated)               xml.etree.ElementTree.ParseError: no element found: line 1, column 20

2. Read the line and column: they tell you which cause it is

Position in the errorWhat it meansTypical cause
line 1, column 0The parser received zero bytesEmpty file; empty string or b'' HTTP body; a file object already read to the end; a StringIO/BytesIO you wrote to and never rewound
Further along, at the end of the inputInput ended before any root element started, or before it was closedWhitespace only; only an <?xml ...?> declaration; only a comment; a truncated document (<root><item>1</item> fails at column 20, its end)

Measured: xml.etree.ElementTree raises ParseError and xml.dom.minidom raises xml.parsers.expat.ExpatError, both with the same expat message -- the module name differs, the meaning doesn't. "no element found" never means your XML has a syntax error in the middle; it means the document ended before a complete root element.

3. Fixes

Related: ElementTree.parse() on a non-blocking stream and xmlrpc.client ServerProxy ExpatError on a valid reply, two less obvious ways to get zero bytes.

Frequently asked questions

What does 'no element found: line 1, column 0' mean?

The parser got no input at all. We measured it for an empty file, an empty string, b'', a file object that had already been read, and a StringIO that was written to without seek(0).

Why does it fail even though the file has valid XML?

Most often because something already read the file object (a debug print(f.read()), a size check, an earlier parse) and the position is at the end. Call f.seek(0) before parsing, or reopen the file.

Is ParseError different from ExpatError?

ElementTree raises xml.etree.ElementTree.ParseError and minidom raises xml.parsers.expat.ExpatError, but both come from the same expat parser with the same message. Catch the one for the module you use.

Does an XML declaration or comment count as an element?

No. We measured that input containing only <?xml version="1.0"?> or only a comment still raises 'no element found' -- a document needs one root element.

I get it when parsing requests' r.content. Why?

r.content is b'' -- ET.fromstring(b'') raises the same 'no element found: line 1, column 0'. Check r.status_code and the headers first; the server sent an empty body.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5's built-in xml.etree.ElementTree, xml.dom.minidom and xml.parsers.expat, parsing empty, whitespace-only, declaration-only, comment-only and truncated input, plus file and StringIO objects that were already at end-of-file. PlainHeaders is our extension; Python's XML modules are built in.