0026 — Migrar leitura de XLSX de POI para FastExcel (native-image)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-11 · Decidido em: 2026-08-19
Supersede a decisão 0002 (leitura via POI/SAX).
Contexto¶
O alvo é deployar o binário GraalVM native-image no lugar do jar — cortar RAM e tempo de
startup no servidor (Coolify). O bloqueio era o Apache POI: ele lê XLSX via XMLBeans, cujo
schema type-system é carregado por reflexão + recursos .xsb e não compila em native-image
(ClassCastException no StylesTable, sem fix estável no GraalVM CE). Enquanto POI estivesse no
grafo — inclusive só nos testes, arrastando o bridge log4j-to-slf4j — não havia native limpo.
A decisão 0002 já resolvia o problema de memória lendo em streaming (SAX, memória ~constante). O requisito novo (native) não invalida aquele — apenas exige outra lib de leitura streaming que compile em native.
Decisão¶
Ler XLSX com FastExcel (org.dhatim:fastexcel-reader), que usa apenas o StAX do JDK (zero
XMLBeans) e compila limpo em native, preservando o streaming linha-a-linha.
ExcelProcessorreescrito sobreReadableWorkbook/Sheet.openStream(), mantendo a semântica de célula idêntica ao reader SAX anterior (string→trim/null; numérico com formato de data→ddMMyyyy; numérico comum→bruto; boolean; erro/vazio→null). A paridade é garantida pelo golden-masterProcessamentoConformidadeTest(Java × Python sobre planilhas reais).- O path DOM legado (
ExcelCellValue,SheetSaxHandler,AbaConfigProcessor,AbaFaturamentoProcessor,AbaMovimentoProcessor) foi removido. O período virou o tipoPeriodoRelatorio. - POI zero, inclusive nos testes: a escrita de fixture XLSX migrou para o FastExcel-writer
(
org.dhatim:fastexcel) via o helper de testeXlsxFixtureWriter. POI elog4j-to-slf4jsaíram dobuild.gradle.ktspor completo. - Build native configurado no
build.gradle.kts(configure<GraalVMExtension>): perfil-PnativeQuick→-Ob(loop de dev), sem a flag →-Os(imagem menor, prod);--gc=serial.reflect-config.jsonregistra oSentryAppenderdo logback (instanciado por reflexão no boot). Evolução (2026-09-11): desde axadm-comum-web0.9.1 essa hint vem no jar da lib, com os métodos, maisSentryOptionseLevel.valueOf; oreflect-config.jsonlocal perdeu as entradas do appender e doSentryOptions.
Consequências¶
- Native-image desbloqueado: dá pra buildar/deployar o binário no lugar do jar.
- Memória ~constante mantida (streaming StAX), como na 0002.
isDateFormaté código nosso (reproduz oDateUtil.isADateFormatdo POI, que saiu do classpath) — coberto por teste unitário direto (ExcelProcessorDateFormatTest).- Ramo de leitura de data numérica sem teste dedicado (defer explícito): as planilhas reais do
cliente exportam datas como texto (verificado nas fixtures: zero célula com formato de data),
e o roundtrip FastExcel writer→reader não preserva a detecção de formato-de-data
(
getDataFormatString()voltanull). Logo não há como montar o input desse ramo via oXlsxFixtureWriter, e o ramo não dispara em produção. É código defensivo; mantido, mas sem teste de ponta a ponta — cobri-lo exigiria um.xlsxbinário forjado por ferramenta externa para código de valor ~zero. Se um dia o cliente passar a exportar datas como células Excel, revisitar.
Correção 2026-09-15: o roundtrip preserva o formato quando o reader abre com
ReadingOptions(true, false); sem otrue, ogetDataFormatString()voltanull. OExcelProcessorpassou a abrir assim, e o ramoasDate()ganhou teste de ponta a ponta com a célula de data escrita peloXlsxFixtureWriter.
Alternativas consideradas¶
- Manter POI só em
testImplementation: deixaria o objetivo (zero POI) pela metade e manteria o bridgelog4j-to-slf4j; sem ganho, já que o FastExcel-writer cobre a escrita de fixture. - Forjar
.xlsxbinário com data real para cobrir o ramoasDate: reintroduz dependência de ferramenta externa (Excel/LibreOffice/POI) e um binário no repo para exercitar código que dado real nunca toca. Descartado (baixo valor). - Remover o ramo
asDate/isDateFormat: perderia a defesa caso o export do cliente mude. Descartado — mantido como defensivo.