0020 — Leitura XLSX: Apache POI → FastExcel¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-08-19 · Decidido em: 2026-08-19
Contexto¶
O alvo é deployar a aplicação como binário GraalVM native-image (RSS ~3× menor, imagem ~9×
menor, build fora do host de produção — spec 012).
O bloqueio era a leitura dos XLSX: ExcelSheetReader (motor SAX sobre XSSFReader/OPCPackage
do Apache POI 5.3.0) + SheetSaxHandler liam via XMLBeans, cujo type-system por reflexão
não compila em native-image (ClassCastException no StylesTable; sem fix no GraalVM CE).
Enquanto POI estivesse alcançável no grafo de runtime, o native não fechava.
O projeto irmão bi-transporte-xls já havia trilhado o caminho (ADR central
0023 — Ler XLSX com FastExcel).
Decisão¶
-
Reader por
org.dhatim:fastexcel-reader:0.20.2(StAX puro, zero XMLBeans).ExcelSheetReaderfoi reescrito preservando as 4 assinaturas públicas (readSheet,readDefaultSheet,readFirstRowOfSheet,listSheetNames) e a semântica de célula byte-a-byte: string →trim(blank→null); boolean →"true"/"false"; erro/vazio → null; numérico com formato de data →ddMMyyyy; numérico comum →getRawValue()(o texto<v>cru — nãoasNumber(), que introduziria.0/notação). A detecção de data POI-free (isDateFormat) reimplementa a lógica doDateUtil.isADateFormat(IDs built-in 14–22/45–47 + custom com tokeny/d/h/s). -
POI removido do projeto inteiro (nem
implementation, nemtestImplementation). As fixtures de teste passaram a ser escritas porXlsxFixtureWriter(FastExcel-writer,org.dhatim:fastexcel). Removido junto o bridgelog4j-to-slf4j, que só existia porque o POI arrastavalog4j-api. -
Paridade garantida por caracterização do reader (
ReaderCaracterizacaoTest). O oráculo é o comportamento POI atual — não o antigo oráculo Python (exportar-json.py, projeto de Automação externo que não existe mais). Para cada um dos 9 tipos, a saídaString[]do reader POI foi congelada como baseline (src/test/resources/caracterizacao/*.txt) e o reader FastExcel deve reproduzi-la idêntico. Um guard falha se algumTipoArquivonão tiver baseline.
Consequências¶
- Native desbloqueado — nenhum XMLBeans no runtime; o rollout native é a Fase B da spec 012.
- Cobertura declarada (REGRA Nº 3): os samples sintéticos são all-string, então a caracterização
prova paridade de indexação de coluna / seleção de aba / trim / vazio→null nos 9 tipos. O ramo
numérico-com-data (
ddMMyyyy) não é alcançável por fixture sintética — o writer não preserva o formato-de-data (getDataFormatString null no roundtrip) — e fica coberto pelos golden-masters reais (RELATORIO18/META/TRR) + o teste unitário direto deisDateFormat. - Golden-masters Java×Python mantidos, não regenerados — o oráculo Python morreu; o
resultado-python.jsoncongelado ainda roda oConformidadeTest, mas fixtures novas desse tipo não podem ser geradas.
Alternativas descartadas¶
- Manter POI só em
testImplementation(permitido pelo guard de CI, que só reprova POI não-test em app native): rejeitada por decisão do usuário — POI zero, alinhado ao piloto. - Escrever fixtures via FastExcel com data numérica estilizada para cobrir o ramo
ddMMyyyy: inviável — o roundtrip writer→reader não preserva a detecção de formato-de-data (achado do piloto).