apache/maven · error · XMLStreamException

Expected root element '${rootTag}' but found '{}'

Error message

Expected root element '${rootTag}' but found '{}'

What it means

Thrown by the STAX reader that modello generates from reader-stax.vm (the template behind readers like MavenXpp3Reader). In read(XMLStreamReader, strict), the first START_ELEMENT's local name must equal the model's root tag (the ${rootTag} placeholder, e.g. 'project' for the POM model); with strict=true any other name aborts the parse and the message reports the element actually found (as a QName, via parser.getName()). It is the reader's first guard against handing a model-specific parser the wrong kind of XML document.

Source

Thrown at src/mdo/reader-stax.vm:276

     * @throws XMLStreamException XMLStreamException if
     * any.
     * @return ${root.name}
     */
#if ( $locationTracking )
    public ${root.name} read(XMLStreamReader parser, boolean strict, InputSource inputSrc) throws XMLStreamException {
#else
    public ${root.name} read(XMLStreamReader parser, boolean strict) throws XMLStreamException {
#end
#if ( $needXmlContext )
        Deque<Object> context = new ArrayDeque<>();
#end
        $rootUcapName $rootLcapName = null;
        int eventType = parser.getEventType();
        boolean parsed = false;
        while (eventType != XMLStreamReader.END_DOCUMENT) {
            if (eventType == XMLStreamReader.START_ELEMENT) {
                if (strict && ! "${rootTag}".equals(parser.getLocalName())) {
                    throw new XMLStreamException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser.getLocation(), null);
                } else if (parsed) {
                    // fallback, already expected a XMLStreamException due to invalid XML
                    throw new XMLStreamException("Duplicated tag: '${rootTag}'", parser.getLocation(), null);
                }
#if ( $locationTracking )
                $rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), inputSrc);
#elseif ( $needXmlContext )
                $rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), context);
#else
                $rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI());
#end
                parsed = true;
            }
            eventType = parser.next();
        }
        if (parsed) {
            return $rootLcapName;
        }

View on GitHub (pinned to e4093d4e12)

Solutions

  1. Verify the input is the document type this reader was generated for and that its root element matches the expected root tag (e.g. <project> for a pom).
  2. Use the reader matching the document: SettingsXpp3Reader for settings.xml, MetadataXpp3Reader for maven-metadata.xml, MavenXpp3Reader for pom.xml.
  3. Peek the root element local name before calling read() and branch to the correct reader.
  4. Only if lenient reading is acceptable, call read(parser, false) - the root-name check is skipped in non-strict mode.

Example fix

// before
Model model = new MavenXpp3Reader().read(in, true);
// in actually held settings.xml -> Expected root element 'project' but found '<settings>'

// after
byte[] bytes = in.readAllBytes();
String root = peekRootLocalName(new ByteArrayInputStream(bytes));
if (!"project".equals(root)) {
    throw new IllegalArgumentException("Not a pom document, root element: " + root);
}
Model model = new MavenXpp3Reader().read(new ByteArrayInputStream(bytes), true);
Defensive patterns

Strategy: validation

Validate before calling

byte[] bytes = in.readAllBytes();
XMLInputFactory f = XMLInputFactory.newFactory();
f.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader peek = f.createXMLStreamReader(new ByteArrayInputStream(bytes));
String root = null;
while (peek.hasNext()) {
    if (peek.next() == XMLStreamReader.START_ELEMENT) { root = peek.getLocalName(); break; }
}
peek.close();
if (!"project".equals(root)) {
    throw new IllegalArgumentException("Not a pom document, root element: " + root);
}
Model model = new MavenXpp3Reader().read(new ByteArrayInputStream(bytes), true);

Type guard

static boolean isRootElementMismatch(XMLStreamException e) {
    return e.getMessage() != null && e.getMessage().startsWith("Expected root element");
}

Try / catch

try {
    Model model = reader.read(parser, true);
} catch (XMLStreamException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Expected root element")) {
        throw new IllegalArgumentException("Wrong document type for this reader: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling read(InputStream/Reader, true) or read(XMLStreamReader, true) on a document whose root element is not the model's root tag: passing settings.xml or maven-metadata.xml to a POM reader, passing a fragment such as <modules> or <dependency> instead of a full document, or a root with the right namespace but a different local name.

Common situations: Routing mixed XML file types through one generated reader; using a reader built from a model version whose root tag changed; feeding POM snippets copied without the <project> wrapper; pipelines passing the wrong variable to read().

Related errors


AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21). Data as JSON: /api/errors/f78a78049195ab1e. Report an issue: GitHub.