Java – Leer XML con SAX Parser

SAX no construye ningún árbol: te va avisando según lee. Gasta una memoria mínima y por eso es la opción cuando el archivo no cabe; a cambio, el estado lo llevas tú.

La idea

DOM carga el documento entero y luego lo recorres. SAX lo lee de principio a fin una sola vez y, por cada cosa que encuentra, llama a un método tuyo: startElement al abrir una etiqueta, characters con el texto, endElement al cerrarla. Nunca hay más de unos kilobytes en memoria, sea el archivo de 2 KB o de 2 GB.

catalogo.xml<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<catalogo>
  <libro id="1">
    <titulo>El Quijote</titulo>
    <autor>Cervantes &amp; Saavedra</autor>
  </libro>
</catalogo>

El manejador

import java.io.File;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

public class Sax {
    public static void main(String[] args) throws Exception {
        var fabrica = SAXParserFactory.newInstance();
        fabrica.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        var manejador = new DefaultHandler() {
            private final StringBuilder texto = new StringBuilder();
            private boolean esHoja = false;

            @Override
            public void startElement(String uri, String local, String nombre, Attributes a) {
                texto.setLength(0);
                esHoja = true;
                if (nombre.equals("libro")) {
                    System.out.println("libro id=" + a.getValue("id"));
                }
            }

            @Override
            public void characters(char[] c, int inicio, int largo) {
                texto.append(c, inicio, largo);
            }

            @Override
            public void endElement(String uri, String local, String nombre) {
                if (esHoja) {
                    System.out.println("  " + nombre + " = " + texto.toString().strip());
                }
                texto.setLength(0);
                esHoja = false;
            }
        };

        fabrica.newSAXParser().parse(new File("catalogo.xml"), manejador);
    }
}
salidalibro id=1
  titulo = El Quijote
  autor = Cervantes & Saavedra

Las dos trampas de SAX

1. characters puede llamarse varias veces para el mismo texto. El analizador entrega lo que tiene en el búfer, y un texto largo, o uno que contiene una entidad como &amp;, llega partido en trozos. Por eso no se puede imprimir dentro de characters: hay que acumular en un StringBuilder y leerlo en endElement, que es cuando ya está completo.
2. Hay que saber si el elemento tiene hijos. Sin la bandera esHoja, al cerrar <libro> y <catalogo> el programa imprime otra vez el último texto acumulado, porque esos elementos cierran después y el búfer sigue lleno. La bandera se pone al abrir cualquier elemento y se quita al cerrar: si al llegar al cierre sigue puesta, es que nadie abrió nada por en medio y por tanto el elemento era una hoja. Esto, en la primera versión de esta página, estaba mal.

SAX o DOM

DOMSAX
Memoriatodo el documentounos kilobytes
Recorridolibre, en cualquier ordenuna pasada, de principio a fin
Volver atrássíno
Modificarsíno, es solo lectura
Códigocortomás largo, llevas tú el estado
Paraconfiguraciones, respuestas de APIexportaciones, archivos enormes

Hay un tercero, StAX (XMLStreamReader), que es también de un solo paso pero al revés: en lugar de que el analizador te llame, eres tú quien pide el siguiente evento. Gasta lo mismo que SAX y el código sale más legible, así que para empezar hoy suele ser mejor opción.

Las dos líneas de seguridad no son opcionales. Un XML puede declarar entidades externas y hacer que el analizador lea archivos del servidor o abra conexiones: es el ataque XXE. Si el XML viene de fuera —de un usuario, de una API, de un correo—, hay que desactivar eso antes de analizar. Con FEATURE_SECURE_PROCESSING y los accesos externos en blanco, el analizador se niega.

Más información en inglés: Read XML with SAX parser.

Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.