StAX Parser in Java: Cursor & Iterator-Based Streaming XML

StAX (Streaming API for XML) is a pull-based streaming XML API. Unlike SAX where the parser pushes events to your handler, StAX lets you pull the next event when you are ready. Java provides two StAX APIs: the cursor API (XMLStreamReader (lower level, highest performance) and the iterator API (XMLEventReader) higher level, event objects). StAX also supports writing XML through XMLStreamWriter.

Share

Two StAX APIs: Cursor vs Iterator

APIClassStylePerformanceBest for
CursorXMLStreamReaderStateful cursor; call next() to advanceHighest: no event object allocationPerformance-critical parsing
IteratorXMLEventReaderReturns XMLEvent objectsSlightly lower: allocates event objectsFiltering, pipelining, cleaner code

Cursor API: XMLStreamReader

Java
import javax.xml.stream.*;
import java.io.*;
import java.util.*;

public class StaxCursorExample {

    public static List<String> parseTitles(InputStream xml) throws XMLStreamException {
        List<String> titles = new ArrayList<>();
        XMLInputFactory factory = XMLInputFactory.newInstance();

        // Disable XXE
        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
        factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

        XMLStreamReader reader = factory.createXMLStreamReader(xml);
        try {
            boolean inTitle = false;

            while (reader.hasNext()) {
                int event = reader.next();

                switch (event) {
                    case XMLStreamConstants.START_ELEMENT -> {
                        inTitle = "title".equals(reader.getLocalName());
                        if ("book".equals(reader.getLocalName())) {
                            // read attribute
                            String id = reader.getAttributeValue(null, "id");
                            System.out.println("Found book id=" + id);
                        }
                    }
                    case XMLStreamConstants.CHARACTERS -> {
                        if (inTitle) titles.add(reader.getText().trim());
                    }
                    case XMLStreamConstants.END_ELEMENT -> {
                        inTitle = false;
                    }
                }
            }
        } finally {
            reader.close();
        }
        return titles;
    }
}
Always close XMLStreamReader in a finally block or use try-with-resources (it implements AutoCloseable in Java 7+). Failing to close it leaks the underlying stream.

XMLStreamConstants: Event Type Reference

ConstantValueMeaning
START_DOCUMENT7Start of document
END_DOCUMENT8End of document
START_ELEMENT1Opening tag
END_ELEMENT2Closing tag
CHARACTERS4Text content (may be whitespace)
CDATA12CDATA section
COMMENT5XML comment
PROCESSING_INSTRUCTION3Processing instruction
SPACE6Ignorable whitespace
ATTRIBUTE10Standalone attribute (rare)

Iterator API: XMLEventReader

Java
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import java.io.*;

public class StaxIteratorExample {

    public static void parse(InputStream xml) throws Exception {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

        XMLEventReader reader = factory.createXMLEventReader(xml);
        try {
            while (reader.hasNext()) {
                XMLEvent event = reader.nextEvent();

                if (event.isStartElement()) {
                    StartElement start = event.asStartElement();
                    String name = start.getName().getLocalPart();

                    if ("book".equals(name)) {
                        Attribute idAttr = start.getAttributeByName(
                                new javax.xml.namespace.QName("id"));
                        System.out.println("Book id: " + (idAttr != null ? idAttr.getValue() : "?"));
                    }

                    if ("title".equals(name)) {
                        // peek at next event (should be Characters)
                        XMLEvent next = reader.nextEvent();
                        if (next.isCharacters()) {
                            System.out.println("Title: " + next.asCharacters().getData().trim());
                        }
                    }
                }

                if (event.isEndElement()) {
                    String name = event.asEndElement().getName().getLocalPart();
                    if ("library".equals(name)) break;   // early exit cleanly
                }
            }
        } finally {
            reader.close();
        }
    }
}

Writing XML: XMLStreamWriter

Java
import javax.xml.stream.*;
import java.io.*;

public class StaxWriteExample {

    public static String writeLibrary() throws XMLStreamException {
        StringWriter sw = new StringWriter();
        XMLOutputFactory factory = XMLOutputFactory.newInstance();
        XMLStreamWriter writer = factory.createXMLStreamWriter(sw);

        writer.writeStartDocument("UTF-8", "1.0");
        writer.writeCharacters("\n");

        writer.writeStartElement("library");
        writer.writeCharacters("\n  ");

        // First book
        writer.writeStartElement("book");
        writer.writeAttribute("id", "1");
        writer.writeAttribute("genre", "technical");

        writer.writeStartElement("title");
        writer.writeCharacters("Effective Java");
        writer.writeEndElement();  // title

        writer.writeStartElement("author");
        writer.writeCharacters("Joshua Bloch");
        writer.writeEndElement();  // author

        writer.writeEndElement();  // book

        writer.writeCharacters("\n");
        writer.writeEndElement();  // library
        writer.writeEndDocument();
        writer.flush();
        writer.close();

        return sw.toString();
    }
}
XMLStreamWriter does not automatically indent output. For pretty-printed XML, set the javax.xml.stream.isRepairingNamespaces property and manually write whitespace characters, or use a DOM Transformer with OutputKeys.INDENT.

Event Filtering: XMLEventFilter

The iterator API supports EventFilter to skip unwanted events before your code sees them.

Java
import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader raw = factory.createXMLEventReader(inputStream);

// Filter: keep only START_ELEMENT and END_ELEMENT events
XMLEventReader filtered = factory.createFilteredReader(raw,
        (EventFilter) event -> event.isStartElement() || event.isEndElement());

while (filtered.hasNext()) {
    XMLEvent event = filtered.nextEvent();
    // only START_ELEMENT and END_ELEMENT events reach here
    if (event.isStartElement()) {
        System.out.println("Start: " + event.asStartElement().getName().getLocalPart());
    }
}
filtered.close();