0023 — Ler XLSX com FastExcel-reader, não Apache POI¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12 · Decidido em: 2026-08-19
Contexto¶
Vários apps da casa leem planilhas XLSX (processadores de dados: BI, integração, importadores). A
biblioteca usada por padrão em Java para isso é Apache POI (poi-ooxml). Com a adoção de
GraalVM native-image como alvo de deploy (0022), POI virou
o único bloqueio técnico real da migração.
POI lê XLSX (formato XSSF) via XMLBeans, cujo type-system é carregado por reflexão + recursos
binários e classes geradas org.openxmlformats.schemas.**. Em native-image (mundo fechado, sem
descoberta em runtime) o XMLBeans não resolve o type-system e cai no tipo genérico → estoura um
ClassCastException no StylesTable (XmlComplexContentImpl cannot be cast to StyleSheetDocument).
Não há caminho estável na Community Edition: não existe reachability-metadata oficial para
POI/XMLBeans, a configuração manual via tracing-agent é frágil (reabre a cada bump de POI), e a issue
upstream que descreve exatamente este caso — oracle/graal#1993
— foi fechada sem solução consolidada.
Decisão¶
Para LER planilhas XLSX, use org.dhatim:fastexcel-reader. NÃO use Apache POI no main.
- FastExcel-reader é native-safe por construção. Ele não carrega XMLBeans nem type-system por
reflexão; parseia o XLSX com um StAX pull-parser (a implementação Aalto,
com.fasterxml:aalto-xml, entra como dependência de compilação) — dependências comuns, amigáveis ao native-image. É por isso que compila e roda em native sem hack nenhum, não porque use "o StAX do JDK". - POI só em
testImplementation. Gerar.xlsxde fixture no teste é uso legítimo de POI — fica fora domain, não entra no binário native.implementation/apidepoi-ooxmlnum app native é proibido (guarda noci.ymlda casa). - Exceção declarada (§1.6 / REGRA Nº 3). FastExcel não cobre estilo rico (cores/fontes/bordas) nem avaliação de fórmula (devolve o valor cacheado). App que depende disso pode usar POI — mas esse app não vai a native (fica JVM, com limite de memória no Coolify). É uma decisão declarada por app, não silêncio: registre o porquê no repo do app.
- Escrever XLSX: preferir
org.dhatim:fastexcel(o writer do mesmo projeto, também sem XMLBeans) antes de recorrer a POI. Esta RD normatiza a leitura; o writer é a preferência análoga.
Correção 2026-09-12: a guarda de POI é do job
gatedopipeline.yml(0027) e sabe que o app é native quando obuild.targetsdodocs/app.jsoncontémnative.
Como se lê (o essencial)¶
Detalhe operacional (tipo neutro, detecção de data, streaming) na receita de
engenharia/java-micronaut §Específico. Em resumo:
a API de célula do FastExcel (asDate() → LocalDateTime, getRawValue(), getDataFormatId(),
getDataFormatString()) cobre o uso típico de leitura; datas se detectam pelos IDs de formato built-in
(14–22 / 45–47) ou pelo token de data no format string, já que o FastExcel não expõe um helper isDate.
A leitura é streaming (Sheet.openStream() → Stream<Row>), preservando o baixo custo de memória.
Alternativas descartadas¶
- POI + reachability-metadata manual (tracing-agent). Frágil e não-consolidado: a configuração reabre a cada bump de POI, não há metadata oficial mantida, e as issues upstream do próprio Graal sobre exatamente este caso foram fechadas sem fix. Custo permanente por um problema que o FastExcel não tem.
- Manter POI e não ir a native. Renuncia ao ganho estratégico (RSS/imagem menores, sem build-spike no host — 0022) por uma biblioteca de leitura substituível. Só se justifica na exceção de estilo-rico/fórmula.
Consequências¶
- Migrar de POI para FastExcel é troca de motor, não de contrato. Isole a leitura numa camada que
produza um tipo neutro (ex.
String[]por linha) preservando a semântica célula-a-célula; os mapeadores de domínio a jusante não mudam. - Paridade é obrigatória antes do native. Um golden-master (fixture real + resultado esperado) tem de passar antes de compilar native — é o gate que garante que trocar o motor não mudou o dado (constituição §6). Ver e2e native.
- O
ci.ymlda casa reprova POI emmainde app native. Guarda estática que pega o bloqueio antes do runtime (o build compila; quebra em runtime). Ver java-micronaut §Armadilhas.