Java EE / Jakarta EE
XML em Java: SAX, DOM, StAX e Jakarta XML Binding (JAXB 4)
Intermediário8 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
XML ainda é o formato de muitas integrações corporativas, arquivos de configuração e padrões de documentos, e o Java oferece três modelos de parsing e um framework de binding para lidar com ele. Este guia compara DOM, SAX e StAX com exemplos que funcionam, explica validação por schema e XPath, mostra como o Jakarta XML Binding (JAXB) mapeia classes para XML nos dois sentidos desde que saiu do JDK e detalha o hardening de parsers de que toda aplicação que consome XML precisa.
O que você vai aprender
- Escolher entre DOM, SAX e StAX de acordo com o tamanho do documento e o padrão de acesso
- Fazer parsing, navegar e escrever XML com as APIs JAXP do módulo java.xml
- Validar documentos contra um XML Schema e consultá-los com XPath
- Fazer marshal e unmarshal de classes Java com as anotações do Jakarta XML Binding 4 e gerar classes a partir de um schema
- Configurar cada parser para evitar ataques de XML External Entity (XXE) e de expansão de entidades
O JSON conquistou a web, mas o XML nunca saiu das empresas. Ele chega como envelopes SOAP, arquivos de extrato bancário, mensagens de padrões setoriais, asserções SAML, documentos de escritório e a própria configuração do servidor de aplicação, e costuma vir de fora da sua fronteira de confiança. A biblioteca padrão do Java faz parsing de XML desde 2002 por meio do JAXP (Java API for XML Processing, hoje o módulo java.xml), que oferece três modelos: uma árvore em memória, um fluxo de eventos empurrados para você ou um fluxo de eventos que você puxa. Por cima deles, o Jakarta XML Binding (JAXB) mapeia XML diretamente para classes Java, de modo que a maior parte do código da aplicação nunca vê um elemento sequer. Este guia cobre as quatro abordagens e, como parsers XML são uma superfície de ataque bem conhecida, as configurações que os tornam seguros.
Três modelos de parsing
| DOM | SAX | StAX | |
|---|---|---|---|
| Modelo | Documento inteiro como árvore em memória | Push: o parser chama o seu handler a cada evento | Pull: você pede o próximo evento ao reader |
| Memória | Proporcional ao tamanho do documento (várias vezes o número de bytes) | Constante | Constante |
| Acesso | Aleatório; navegar, modificar, gravar de volta | Só para a frente, somente leitura | Só para a frente; API de escrita separada |
| Facilidade | O mais fácil para documentos pequenos | O mais difícil: máquina de estados em callbacks | Código linear, fácil de interromper ou delegar |
| Use para | Configuração, mensagens pequenas, documentos que você edita ou consulta com XPath | Arquivos muito grandes, extração de poucos valores, código legado | Arquivos grandes, transformações em streaming, escrita de XML à mão |
Os três vêm de factories em javax.xml.parsers e javax.xml.stream. Os pacotes javax.xml são Java SE, não Jakarta EE, e não migraram para jakarta.*; só a API de binding migrou.
DOM: a árvore
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // no DTD, no XXE
factory.setExpandEntityReferences(false);
Document doc = factory.newDocumentBuilder().parse(new File("orders.xml"));
NodeList lines = doc.getElementsByTagNameNS("urn:shop:orders", "line");
for (int i = 0; i < lines.getLength(); i++) {
Element line = (Element) lines.item(i);
String sku = line.getAttribute("sku");
int qty = Integer.parseInt(line.getElementsByTagNameNS("urn:shop:orders", "qty").item(0).getTextContent());
}
A API DOM do W3C é verbosa (NodeList não é iterável, o texto é um nó filho e não uma propriedade, e os espaços entre elementos aparecem como nós de texto), mas entrega o documento inteiro para você percorrer, modificar (createElementNS, appendChild, removeChild) e serializar de volta com um Transformer. Sempre chame setNamespaceAware(true): o padrão é false por razões históricas e, sem isso, buscas qualificadas por namespace simplesmente não retornam nada, sem nenhum aviso.
SAX: eventos empurrados
Um parser SAX lê o documento uma única vez e, à medida que avança, chama métodos de um ContentHandler (startElement, characters, endElement). O seu handler guarda o estado de que precisar:
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
class TotalQuantityHandler extends DefaultHandler {
private final StringBuilder text = new StringBuilder();
private boolean inQty;
long total;
@Override public void startElement(String uri, String local, String qName, Attributes atts) {
inQty = "qty".equals(local);
text.setLength(0);
}
@Override public void characters(char[] ch, int start, int length) {
if (inQty) text.append(ch, start, length); // may be called several times per element
}
@Override public void endElement(String uri, String local, String qName) {
if (inQty) { total += Long.parseLong(text.toString().trim()); inQty = false; }
}
}
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
var handler = new TotalQuantityHandler();
spf.newSAXParser().parse(new File("orders.xml"), handler);
O detalhe que quebra handlers ingênuos: characters pode entregar o texto de um mesmo elemento em vários pedaços, então você precisa acumulá-lo e ler o resultado em endElement. O SAX é o modelo mais rápido e enxuto, e o que está por baixo da maioria dos outros parsers, mas a inversão de controle torna trabalhoso tudo o que vai além de uma extração simples.
StAX: eventos puxados
O StAX (javax.xml.stream) inverte o fluxo: você chama next() e inspeciona o evento. A mesma extração se lê de cima para baixo:
import javax.xml.stream.*;
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); // XXE hardening
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
long total = 0;
try (var in = new FileInputStream("orders.xml")) {
XMLStreamReader r = xif.createXMLStreamReader(in);
while (r.hasNext()) {
if (r.next() == XMLStreamConstants.START_ELEMENT && "qty".equals(r.getLocalName())) {
total += Long.parseLong(r.getElementText()); // reads the text and consumes the end tag
}
}
r.close();
}
O XMLStreamWriter é o par de escrita: writeStartElement, writeAttribute, writeCharacters, writeEndElement, com o escaping resolvido para você. Montar XML concatenando strings é o caminho para documentos inválidos e bugs de injeção; use o writer, o DOM ou o JAXB.
Validação, XPath e transformação
A validação com XML Schema (XSD) confere a estrutura e os tipos de um documento antes que o seu código confie nele. Carregue o schema uma vez com uma SchemaFactory e, então, valide um documento já parseado ou associe o schema à factory do parser para que a validação aconteça durante o parsing:
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // no fetching of remote schemas
Schema schema = sf.newSchema(new File("orders.xsd"));
schema.newValidator().validate(new StreamSource(new File("orders.xml"))); // throws SAXException on violation
O XPath (javax.xml.xpath) consulta um DOM: xpath.evaluate("sum(//o:line/o:qty)", doc) com um NamespaceContext que mapeia o prefixo o. É o jeito mais rápido de extrair alguns valores de um documento que já está em memória, e o mais lento de processar um documento grande, porque cada avaliação percorre a árvore. O XSLT, via javax.xml.transform, converte XML em XML, HTML ou texto; o mesmo Transformer sem stylesheet serializa um DOM para um stream, e é assim que se grava de volta um documento modificado.
Jakarta XML Binding
Para documentos estruturados com formato conhecido, lidar com elementos é esforço desperdiçado. O JAXB mapeia uma hierarquia de classes para XML, de modo que ler um documento é uma chamada de método e escrevê-lo é outra.
Onde a biblioteca está hoje
O JAXB vinha embutido no JDK do Java 6 ao Java 10 e foi removido no Java 11 pela JEP 320, junto com o JAX-WS e os demais módulos Java EE. Em qualquer JDK atual ele é uma dependência comum e, desde o Jakarta EE 9, o pacote é jakarta.xml.bind:
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId> <!-- the reference runtime; scope runtime -->
<version>4.0.5</version>
<scope>runtime</scope>
</dependency>
O XML Binding 4.0 é uma parte opcional da plataforma Jakarta EE 10 e não faz parte do Web Profile, então verifique se o seu servidor o inclui (os servidores de plataforma completa geralmente incluem) e, caso contrário, adicione a dependência à aplicação. Código mais antigo que importa javax.xml.bind precisa ter os imports reescritos e os artefatos trocados; fora isso, a API é idêntica.
Anotando as classes
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import jakarta.xml.bind.annotation.*;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement(name = "order", namespace = "urn:shop:orders")
@XmlAccessorType(XmlAccessType.FIELD) // map fields, ignore getters: predictable and no setter needed
@XmlType(propOrder = { "reference", "placed", "lines" })
public class OrderDocument {
@XmlAttribute(required = true)
String id;
@XmlElement(required = true)
String reference;
@XmlElement
@XmlJavaTypeAdapter(LocalDateAdapter.class) // java.time is not built in; adapter converts to/from text
LocalDate placed;
@XmlElementWrapper(name = "lines")
@XmlElement(name = "line")
List<Line> lines = new ArrayList<>();
@XmlTransient
BigDecimal cachedTotal; // not part of the document
public static class Line {
@XmlAttribute String sku;
@XmlElement int qty;
@XmlElement BigDecimal unitPrice;
}
}
Algumas poucas regras determinam a maior parte do mapeamento. Toda classe vinculada precisa de um construtor sem argumentos. @XmlRootElement marca as classes que podem ser raiz de um documento; os tipos aninhados só precisam ser alcançáveis. Sem @XmlAccessorType(FIELD), o JAXB vincula propriedades JavaBean, o que significa que cada campo precisa de um getter e de um setter, e que uma classe com um campo e uma propriedade de mesmo nome acaba mapeando-o duas vezes. @XmlElementWrapper dá às coleções um elemento envolvente (<lines><line/><line/></lines>); sem ele, os <line> repetidos ficam diretamente sob o elemento pai. Subclasses de XmlAdapter cuidam de qualquer tipo que o JAXB não conheça, inclusive java.time, que ele, ao contrário de JPA e JSON-B, ainda não suporta nativamente.
Marshalling e unmarshalling
import jakarta.xml.bind.*;
import javax.xml.stream.XMLInputFactory;
import javax.xml.transform.stream.StreamSource;
JAXBContext context = JAXBContext.newInstance(OrderDocument.class); // expensive: create once, it is thread-safe
// Writing
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.marshal(order, new File("order-42.xml"));
// Reading, through a hardened StAX reader so no DTD is ever processed
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
var reader = xif.createXMLStreamReader(new StreamSource(new File("incoming.xml")));
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema); // validate while unmarshalling
OrderDocument incoming = (OrderDocument) unmarshaller.unmarshal(reader);
O JAXBContext monta o modelo de mapeamento por reflexão e é o objeto caro; Marshaller e Unmarshaller são baratos e não são thread-safe, então crie um por operação. Defina um Schema no unmarshaller sempre que existir um schema: sem ele, o JAXB ignora silenciosamente elementos desconhecidos e aceita a ausência de elementos obrigatórios, o que é conveniente para leitores tolerantes e perigoso para qualquer coisa que importe. unmarshal com tratamento de JAXBElement (unmarshal(source, OrderDocument.class)) cobre documentos cujo elemento raiz não tem uma classe com @XmlRootElement.
Gerando classes a partir de um schema
Quando o schema vem primeiro (um padrão setorial, um contrato com um parceiro), gere as classes com o xjc, o compilador de schemas que acompanha a distribuição do runtime JAXB e está disponível via plugins Maven. Ele produz uma classe por tipo complexo, uma ObjectFactory com métodos de fábrica e um package-info.java com o namespace. O caminho inverso, o schemagen, deriva um XSD de classes anotadas. Código gerado deve ser tratado como saída do build, regenerado a partir do schema e nunca editado à mão; as customizações vão em um arquivo de binding (.xjb) que renomeia classes, mapeia tipos ou associa adapters.
Protegendo todos os parsers
Por padrão, um parser XML faz o que o documento manda. Um documento pode declarar uma entidade externa que aponta para um arquivo local ou uma URL interna e, quando o parser a expande, o conteúdo do arquivo vai parar nos seus dados ou a requisição sai do seu servidor: é a vulnerabilidade XXE, ainda listada em security misconfiguration no OWASP Top 10. Um documento também pode definir entidades que se expandem exponencialmente (o ataque "billion laughs") e esgotar a memória. A defesa é a mesma em todo lugar: recusar DTDs e, se isso não for possível, recusar entidades externas e o acesso a DTDs e schemas externos.
| Parser | Configuração |
|---|---|
DocumentBuilderFactory, SAXParserFactory | setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); caso contrário, as features external-general-entities e external-parameter-entities como false, setXIncludeAware(false), setExpandEntityReferences(false) |
XMLInputFactory (StAX) | SUPPORT_DTD como false, IS_SUPPORTING_EXTERNAL_ENTITIES como false |
SchemaFactory, TransformerFactory, Validator | ACCESS_EXTERNAL_DTD, ACCESS_EXTERNAL_SCHEMA, ACCESS_EXTERNAL_STYLESHEET como "" |
| Todos | XMLConstants.FEATURE_SECURE_PROCESSING como true (limita a expansão de entidades; ativo por padrão desde o Java 7, mas defina explicitamente) |
| JAXB | Fazer unmarshal a partir de uma fonte StAX ou SAX protegida, e não diretamente de um File ou InputStream |
Desde o Java 9, esses limites também podem ser definidos para toda a JVM em $JAVA_HOME/conf/jaxp.properties, e o parser embutido no JDK aplica FEATURE_SECURE_PROCESSING por padrão; mas parsers de terceiros no classpath (Xerces, Woodstox) substituem o do JDK via service loader, e os padrões deles são outros. Defina as features no código, em cada factory, e escreva um teste que envie um documento com um <!DOCTYPE> declarando uma entidade externa e verifique que ele é rejeitado. Implementações de Jakarta REST e stacks SOAP fazem o parsing de XML por você; confira se a configuração delas tem os mesmos ajustes em vez de presumir.
Erros comuns
- Parsing sem suporte a namespaces.
getElementsByTagNameNSnão retorna nada e XPath com prefixos falha. ChamesetNamespaceAware(true)em todas as factories. - Ler
charactersuma única vez. O texto chega em pedaços; acumule atéendElement. - DOM para arquivos de gigabytes.
OutOfMemoryError. Use streaming com StAX ou SAX. - Montar XML concatenando strings.
<e&sem escape geram documentos inválidos e permitem que a entrada do usuário injete elementos. Use um writer, DOM ou JAXB. - Um único
Marshallercompartilhado entre threads. Saída corrompida. Compartilhe oJAXBContexte crie marshallers a cada chamada. - Unmarshal sem schema. Documentos quebrados aceitos silenciosamente. Defina o schema e trate os erros de validação.
javax.xml.bindem um JDK moderno.ClassNotFoundExceptionem tempo de execução. Adicione os artefatos Jakarta e reescreva os imports.- Configurações padrão do parser com entrada não confiável. XXE e expansão de entidades. Proteja todas as factories e teste isso.
Principais pontos
- O JAXP, no módulo
java.xml, oferece DOM (árvore, acesso aleatório, alto consumo de memória), SAX (eventos empurrados, memória mínima, fluxo de controle desajeitado) e StAX (eventos puxados, código linear e ainda um writer). - Use DOM para documentos pequenos que você consulta ou edita, StAX para tudo o que for grande ou vier em streaming, e SAX principalmente ao manter código existente.
- Valide documentos não confiáveis contra um schema e use XPath para extrações pontuais de documentos que já estão em memória.
- O Jakarta XML Binding 4 mapeia classes para XML com
@XmlRootElement,@XmlElement,@XmlAttributee adapters; ele vive emjakarta.xml.bindcomo dependência separada desde que a JEP 320 o removeu do JDK. - Compartilhe um único
JAXBContext, crie umMarshallerouUnmarshallerpor operação, defina um schema ao fazer unmarshal e gere as classes comxjcquando o schema vier primeiro. - Proteja cada factory de parser contra XXE (proíba DTDs, bloqueie entidades externas e o acesso a DTDs e schemas externos) e faça unmarshal a partir de uma fonte protegida.
Fontes e leituras complementares
Perguntas frequentes
- Por que meu código JAXB falha com ClassNotFoundException no Java 11 ou posterior?
- O JAXB fez parte do JDK do Java 6 ao Java 10 como javax.xml.bind e foi removido no Java 11 pela JEP 320. Adicione-o como dependência: para a linha Jakarta atual, jakarta.xml.bind:jakarta.xml.bind-api (4.0.x) mais o runtime org.glassfish.jaxb:jaxb-runtime, e troque os imports para jakarta.xml.bind. Os antigos artefatos javax.xml.bind 2.3 ainda funcionam em JDKs modernos, mas não recebem mais manutenção.
- Quando usar SAX em vez de DOM?
- Quando o documento é grande em relação à memória disponível, quando você só precisa extrair alguns valores ou quando processa um fluxo que nunca termina. O DOM monta a árvore inteira em memória (normalmente várias vezes o tamanho do arquivo) e oferece acesso aleatório; SAX e StAX leem para a frente uma única vez, com memória constante. Para a maioria dos documentos de até alguns megabytes, a praticidade do DOM compensa; para feeds, exportações e uploads de tamanho desconhecido, use streaming.
- XML ainda é relevante quando tudo é JSON?
- Em integração corporativa, sim. Serviços SOAP, padrões bancários e de seguros, schemas setoriais, documentos de escritório, a configuração do Maven e dos servidores de aplicação, asserções SAML e muitas interfaces com parceiros são XML. Saber fazer parsing dele com segurança importa mais, e não menos, agora que ele costuma ficar nas mãos de desenvolvedores que raramente o veem.
- Qual é a diferença entre StAX e SAX?
- Os dois trabalham em streaming. O SAX empurra eventos para callbacks que você implementa, então é o parser que conduz o fluxo e o seu código precisa guardar estado entre os callbacks. O StAX deixa o seu código puxar o próximo evento quando quiser, o que mantém a lógica de parsing linear e facilita parar mais cedo ou passar o reader para outro método. O StAX também tem uma API de escrita; o SAX só lê.