Anti-patrones en Java: leer y escribir XML a mano

2 min de lectura

Leer un valor de un XML con indexOf y substring, o fabricar el documento sumando cadenas, parece más rápido que usar un analizador. Funciona con el primer ejemplo que pruebas y falla con el tercero. Lo comprobamos con seis casos, todos XML válidos.

Leer a mano

Esta es la versión que suele aparecer, con el error que trae casi siempre:

String extraer(String xml) {
    int inicio = xml.indexOf("<nombre>");
    int fin = xml.indexOf("</nombre>");
    return xml.substring(inicio, fin);
}

Devuelve <nombre>Ana: indexOf da la posición donde empieza la etiqueta, y hay que saltársela. Corregido eso, la probamos con seis documentos que cualquier analizador lee sin problema:

String[] casos = {
    "<persona><nombre>Ana</nombre></persona>",
    "<persona><nombre>Gómez &amp; Hijos</nombre></persona>",
    "<persona><nombre><![CDATA[<b>Ana</b>]]></nombre></persona>",
    "<persona><nombre >Ana</nombre></persona>",
    "<persona>\n  <!-- <nombre>viejo</nombre> -->\n  <nombre>Ana</nombre>\n</persona>",
    "<p:persona xmlns:p=\"urn:x\"><p:nombre>Ana</p:nombre></p:persona>",
};
java AMano.javaoriginal: [<nombre>Ana]
[Ana]
[Gómez &amp; Hijos]
[<![CDATA[<b>Ana</b>]]>]
[a><nombre >Ana]
[viejo]
excepción: Range [7, -1) out of bounds for length 63
CasoResultado a manoQué pasó
SimpleAnaBien
Con una entidadGómez &amp; HijosNo traduce &amp; a &
Con CDATA<![CDATA[…]]>Devuelve el envoltorio en vez del contenido
Con un espacio en la etiquetaa><nombre >Ana<nombre > es válido, pero indexOf no lo encuentra y devuelve basura
Con un comentario delanteviejoLee el nombre que estaba comentado
Con espacio de nombresexcepción<p:nombre> no coincide y el programa se cae

Cinco de seis mal, y lo peor es que solo uno avisa. Los demás devuelven un valor equivocado sin ningún error.

Leer con un analizador

El JDK trae uno, así que no hace falta añadir ninguna dependencia:

String nombre(String xml) throws Exception {
    var f = DocumentBuilderFactory.newInstance();
    f.setNamespaceAware(true);
    var doc = f.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
    var nodos = doc.getElementsByTagNameNS("*", "nombre");
    return nodos.getLength() == 0 ? null : nodos.item(0).getTextContent();
}
java ConParser.java[Ana]
[Gómez & Hijos]
[<b>Ana</b>]
[Ana]
[Ana]
[Ana]

Los seis bien. El analizador traduce las entidades, abre el CDATA, ignora los comentarios y entiende los espacios de nombres (por eso se activa setNamespaceAware y se busca con "*").

Escribir a mano

Construir el documento concatenando tiene el problema contrario: hay caracteres que en XML tienen significado y deben escaparse.

String aMano(String nombre, String atributo) {
    return "<root>"
         + "<nombre atr=\"" + atributo + "\">"
         + nombre
         + "</nombre>"
         + "</root>";
}

Con nombre = "Gómez & Hijos <S.A.>" y un atributo con comillas, el resultado ni siquiera es XML:

salida<root><nombre atr="dijo "hola"">Gómez & Hijos <S.A.></nombre></root>
[Fatal Error] :1:26: Element type "nombre" must be followed by either attribute specifications, ">" or "/>".
al leerlo: Element type "nombre" must be followed by either attribute specifications, ">" or "/>".

La línea [Fatal Error] la escribe el analizador por su cuenta en la salida de errores, antes de lanzar la excepción. Este es el error que verá quien reciba el archivo.

Escribir con el DOM

var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
var root = doc.createElement("root");
var n = doc.createElement("nombre");
n.setAttribute("atr", atributo);
n.setTextContent(nombre);
root.appendChild(n);
doc.appendChild(root);

var t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
var out = new StringWriter();
t.transform(new DOMSource(doc), new StreamResult(out));
salida<root>
  <nombre atr="dijo &quot;hola&quot;">Gómez &amp; Hijos &lt;S.A.&gt;</nombre>
</root>

de vuelta: [Gómez & Hijos <S.A.>] atr=[dijo "hola"]

El &, los < > y las comillas se escapan solos. Al volver a leerlo se recupera exactamente lo que se escribió, y eso es lo que hay que exigir a cualquier forma de generar XML.

La versión original de esta entrada usaba JDOM, una biblioteca que sigue existiendo (JDOM 2) y tiene una API más cómoda que el DOM del JDK. Para documentos pequeños como estos no compensa añadir la dependencia; para leer archivos muy grandes sin cargarlos enteros en memoria, mira SAX.

Entrada publicada en 2011 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.

Deja un comentario