EasyExcel 归档之后,该迁移到 Apache Fesod 吗?


EasyExcel 归档之后,该迁移到 Apache Fesod 吗?

前言

组里上周有人问,EasyExcel 都归档了,我们是不是该换个库。问题本身不奇怪,我花了两天把几个仓库、POM 和官方文档翻了一遍,最后给组里的答复是:不用换,至少现在不用。

顺带说一句,这类话题里转来转去的版本号、坐标和协议,错得比想象中多,转述几手之后细节就走样了。下面只写我自己核对过的东西:这三个库各自是什么、关系怎么变的、真要迁代码要动哪里,以及我们自己的项目为什么决定先不动。


一、EasyExcel 为什么停了

EasyExcel 是阿里 2018 年开源的。它当年能起来,靠的是一个实打实的技术点:用 SAX 流式解析替代「把整个工作簿读进内存」。在这之前,POI 的默认用法是 user model,几十万行直接把堆顶爆。EasyExcel 改成事件回调逐行读,内存占用和文件大小解耦,报表导出、批量导入这种场景一下就顺了。它后来成为这类需求的首选,原因就在这。

把这两条路线摊开看会更清楚。POI 的 user model 会把整个工作簿解析成 XSSFWorkbook、Sheet、Row、Cell 这一棵对象树,全部驻留内存,行数一多就撑不住。SAX 反过来,把文件当成一串事件:开始一行、读一个单元格、结束一行。你注册回调,框架按顺序推给你,同一时刻内存里只有当前这一行。差别不在写法好不好看,而在内存占用能不能和文件大小脱钩。几十万行的导出,只有后者跑得动。

转折出现在核心作者身上。大约 2023 年,EasyExcel 的核心作者离开阿里。作者一走,项目基本进入事实上的停滞。停滞期的表现很具体:issue 有人提,但很少看到维护者回应,PR 挂着不动,版本号长期不跳。对一个已经被大量项目引用的库来说,这种状态能拖上好几年,因为存量用户不会因为不更新就立刻搬走。

最后一个版本是 4.0.3,2024 年 9 月发布。这次升级把底层 POI 从 4.1.2 提到了 5.2.5,并且支持 JDK 21。也就是说,它在停止维护之前,做的最后一件事恰好是把「JDK 21 上能不能用」这个问题解决掉。再往后,2025 年 9 月 4 日,alibaba/easyexcel 仓库被归档,变成只读。

归档这件事本身也不难理解。一个开源项目的主要维护者离开,如果没有组织接手,代码放在那里慢慢冷却,是挺常见的结果。与其挂着一个没人管的仓库,不如把它标成只读,至少让后来的人一眼看出它的状态。对使用者来说,这比一个看起来还在更新、实际没人维护的库要诚实。

关于归档,我想把话说清楚一点。归档不等于有漏洞,归档只等于不再有新功能。判断一个冻结的依赖能不能继续留,我会看三件事:当前版本有没有被公开的 CVE 影响,它的底层依赖有没有同样的问题,以及它是否还覆盖得住现有需求。这三条过了,归档这个状态本身就不构成换库的理由。把归档直接等同于必须换,是挺常见的误解。


二、接棒的是 FastExcel

EasyExcel 停了之后,接着往下做的是 FastExcel。主导的人是庄家钜,他本来就是 EasyExcel 的维护者之一,不是外面随便来的人。

坐标是 cn.idev.excel:fastexcel,最新版本 1.3.0,2025 年 8 月发布。它不是随手 fork 出来的一条分支,API 和 EasyExcel 保持兼容。这个兼容性挺关键:从 EasyExcel 迁到 FastExcel,绝大多数调用只需要改 import 和入口类,业务逻辑不用重写。这也是它能在短时间内被接受的原因。

API 兼容这四个字落到代码上,就是包名和入口类对得上,方法签名基本一致,链式调用的写法也一样。它不是把 EasyExcel 推倒重写一遍再换个名字,而是同一套调用方式往下延续。所以迁移的主要成本不在理解新 API,而在把散落各处的 import 换掉。

协议这里要提一下,FastExcel 是 Apache-2.0。不少人下意识以为它是 MIT,其实是错的。对企业项目来说协议不是小事,Apache-2.0 和 MIT 虽然都算宽松协议,但在专利授权、商标和衍生作品的处理上有差别。走合规流程的时候,协议写错会让整个评估建在一个错误的前提上。顺便说,EasyExcel、FastExcel、Fesod 这三个全部是 Apache-2.0。

还有一个细节能说明「捐给 Apache」这件事是真的:github.com/fast-excel/fastexcel 现在会重定向到 apache/fesod。一个个人组织下的仓库指向了 Apache 基金会下的仓库,这就是代码归属发生转移的直接痕迹。

由维护者接手,比被完全陌生的人 fork 让人放心一些。他熟悉原来的设计取舍,也知道哪些历史包袱不能动。走 API 兼容这条路线本身就说明,他想的不是另起炉灶,而是把原来那套东西接着维护下去。


三、Apache Fesod

Apache Fesod 是捐给 ASF 之后的名字。podling 的起始时间是 2025 年 9 月 17 日,仓库在 github.com/apache/fesod。

Fesod 这个名字是 “fast easy spreadsheet and other documents” 的缩写,快速、简单的电子表格和其他文档。名字本身就说明了定位:EasyExcel 那套快和简单的体验保留,同时往其他文档格式扩展。

它和 POI 的关系容易被搞混,这里说清楚。Fesod 是一个独立的 Apache 孵化项目,不是 Apache POI 的子项目,它只是把 POI 5.5.1 当成底层的读写引擎来用。这两者一个是上层封装,一个是底层引擎,Fesod 有自己的 committer、自己的 podling、自己的发版节奏。把它当成 POI 的子项目,会让人误以为它的路线图和治理挂在 POI 下面,实际上不是。

坐标方面,artifact 是 org.apache.fesod:fesod-sheet。注意,并不存在一个叫 org.apache.fesod:fesod 的 artifact,照这个坐标去写 POM,Maven 会直接报找不到依赖,编译都进不去。它的兄弟 artifact 还有 fesod-common、fesod-bom、fesod-parent、fesod-shaded、fesod-examples。

入口类是 FesodSheet,包根在 org.apache.fesod.sheet,工具类在 org.apache.fesod.common。官方文档里给的就是 FesodSheet.read 和 FesodSheet.write。

版本目前走到三个:

  • 2.0.0-incubating,2026 年 1 月
  • 2.0.1-incubating,2026 年 2 月 11 日
  • 2.0.2-incubating,2026 年 5 月 30 日,最新

真要用它,有两个坑得先知道。

第一,IP 清理还在进行。Fesod 的 POM 里写了,从 Alibaba EasyExcel 派生过来的代码,许可证头更新和 IP 清理工作仍在进行中。这是 Apache 孵化项目的常规动作,但它意味着项目这个阶段还在整理来源,不是最终形态。第二,Fesod 会打包 POI。README 里专门警告过:如果你的项目本身已经引入了 POI,引入 Fesod 时必须手动排除 POI 相关的 jar,否则会撞版本冲突。

这里说的孵化是 Apache 的一个正式阶段。项目进孵化器之后,要按 ASF 的治理、法务和发布流程走一遍,通过评审才能毕业成顶级项目。孵化期的项目可以用,但发布节奏和治理成熟度都还在路上。IP 清理处理的是代码来源,每一段从别处搬来的代码,版权归属和许可证头都得交代清楚,Apache 对这一块卡得严,所以这个阶段会拖上一阵。

迁移路径还有一点要注意。官方给的迁移指南是 FastExcel 迁到 Fesod,不是从 EasyExcel 直接迁,中间为 FastExcel 保留了 FastExcel、FastExcelFactory 这类已废弃的别名作为过渡。FastExcel 本来就是 EasyExcel 的延续,入口类和方法签名都没大改,从它再往 Fesod 走改动面最小。EasyExcel 用户直接跳到 Fesod,等于要一次性处理两个阶段的差异,所以实际上是要跨两层。

EasyExcel → FastExcel → Apache Fesod 演进时间线


四、三者的坐标、包名和入口类

这一节是三者的直接对照,放一张表:

维度 EasyExcel FastExcel Apache Fesod
坐标 com.alibaba:easyexcel:4.0.3 cn.idev.excel:fastexcel:1.3.0 org.apache.fesod:fesod-sheet:2.0.2-incubating
根包名 com.alibaba.excel cn.idev.excel org.apache.fesod.sheet
入口类 EasyExcel FastExcel FesodSheet
协议 Apache-2.0 Apache-2.0 Apache-2.0
最新版本 4.0.3(2024-09) 1.3.0(2025-08) 2.0.2-incubating(2026-05-30)
维护状态 已归档(2025-09-04),只读 活跃,已捐给 ASF Apache 孵化中
迁移成本 不适用(已归档) 低(API 兼容,改包名 + 入口类) 中到高(跨两层 + 扩展点需重写验证)

三个库的底层都是 POI,只是封装层次不同。EasyExcel 4.0.3 用的是 POI 5.2.5,Fesod 用的是 POI 5.5.1。这个 POI 版本差不是小事,后面讲扩展点的时候会回到这里:一旦你的代码摸到 POI 那一层,这个版本差就意味着要重新验证。

对照的时候别只盯着版本号新旧。引一个 Excel 库,实际上把 POI 和它那一串传递依赖也引了进来,所以换库时真正决定成本的是两件事:你的代码摸到了哪一层,以及底层引擎换了多少。只用到基础读写,三个库换起来都轻松;摸到 handler 或者直接调 POI,成本就取决于底层 POI 版本差了多少。EasyExcel 到 Fesod,这一跳是从 POI 5.2.5 到 5.5.1。

三方对照与迁移路径


五、真要迁的话,改哪些地方

动手之前,先把自己代码里的使用面摸清楚。我的做法是搜 import,把 com.alibaba.excel 和 org.apache.poi 这两个前缀都过一遍,再看有没有类实现了 handler 接口。这一步花不了多少时间,但它能直接告诉你这次迁移是机械替换,还是要重写加验证。摸完之后再动 POM。

下面按依赖、代码、扩展点三层来拆,能改的和要验的分开看。

先说依赖。假设你原来在根 POM 里统一管 EasyExcel 4.0.3,迁移时先换坐标,注意 artifact 是 fesod-sheet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 迁移前:EasyExcel 4.0.3 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>

<!-- 迁移后:Apache Fesod 2.0.2-incubating -->
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod-sheet</artifactId>
<version>2.0.2-incubating</version>
<exclusions>
<!-- Fesod 内部打包了 POI,项目里若已自带 POI 必须排除,避免版本冲突 -->
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
</exclusions>
</dependency>

用 fesod-bom 统一管版本会省事一点,但排除 POI 这件事不能省。

import 和入口类的替换是机械的。三个库的根包名和入口类一一对应:

1
2
3
4
5
6
7
8
9
10
11
// EasyExcel
import com.alibaba.excel.EasyExcel;
EasyExcel.write(outputStream, OrderExportVO.class).sheet("订单明细").doWrite(list);

// FastExcel
import cn.idev.excel.FastExcel;
FastExcel.write(outputStream, OrderExportVO.class).sheet("订单明细").doWrite(list);

// Apache Fesod
import org.apache.fesod.sheet.FesodSheet;
FesodSheet.write(outputStream, OrderExportVO.class).sheet("订单明细").doWrite(list);

@ExcelProperty 注解的简单名在三者之间是一致的,所以 DTO 上的注解通常不用动,改 import 就行。

下面是一个迁到 Fesod 之后的导出示例,写法上刻意和 EasyExcel 保持一致,方便对照:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.apache.fesod.sheet.FesodSheet;
import org.apache.fesod.sheet.annotation.ExcelProperty;

import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.List;

/**
* 订单导出:查询订单后交给 FesodSheet 写出。
*/
public class OrderExportService {

/** 导出 DTO:@ExcelProperty 的简单名与 EasyExcel 一致,迁移时无需改动 */
public static class OrderExportVO {
@ExcelProperty("订单号")
private String orderNo;

@ExcelProperty("金额")
private BigDecimal amount;

// getter / setter 省略
}

/**
* 查询订单并写出到输出流。
*
* @param os 输出流,通常直接来自 HTTP 响应
*/
public void export(OutputStream os) {
// 业务侧仍是普通分页查询,Excel 侧只负责把结果写出
List<OrderExportVO> orders = loadOrders();

// 入口类换成 FesodSheet,其余调用链与 EasyExcel 保持一致
FesodSheet.write(os, OrderExportVO.class)
.sheet("订单明细")
.doWrite(orders);
}

private List<OrderExportVO> loadOrders() {
// 实际项目里换成 Mapper 查询
return List.of();
}
}

导入侧也一样,基本是机械替换:

1
2
3
4
List<OrderImportDTO> rows = FesodSheet.read(inputStream)
.head(OrderImportDTO.class)
.sheet()
.doReadSync();

读取这一侧,doReadSync 会一次性返回解析好的对象列表,适合数据量可控的导入;数据量大的话,仍然建议用监听器逐行处理,别把整张表读进内存再落库。迁到 Fesod 之后,这套读法没有变化,改的还是 import 和入口类。

到这里看着挺轻松,但「换包名就行」这句话只对基础读写成立,真正的工作量在扩展点上。

另外,import 不一定都摆在明面上。有些项目会把 EasyExcel 的类型藏在泛型或者反射里,grep 看不出来,这种情况只能靠编译器和测试兜底。

一旦你用到写处理器这类扩展点,或者代码里直接 import 了 org.apache.poi.*,迁移就不是改包名那么简单了。常见的几类:

扩展点 迁移时需要做什么
CellWriteHandler 逐个 handler 重新验证回调时机与 API 签名
SheetWriteHandler 重新验证 sheet 生命周期回调
AbstractRowHeightStyleStrategy 自定义行高策略需重新验证
OnceAbsoluteMergeStrategy 合并策略需重新验证
WriteCellData + ImageData 图片嵌入链路需重新验证
直接使用 org.apache.poi.*XSSFSheetPrintSetupCellRangeAddress POI 从 5.2.5 升到 5.5.1,需按新版本重新验证

最后一行最容易被忽略。只要代码里直接 import 了 org.apache.poi.*,POI 从 5.2.5 到 5.5.1 这段迁移就躲不掉,得按新版本重新验证。EasyExcel 的扩展点很多时候就是把 POI 对象直接暴露给你,所以在真实项目里,「用 EasyExcel 的 handler」和「直接调 POI」往往是混在一起的。

表里没有一项是难改的,难的是验证。改 import 是几分钟的事,但要让每个 handler 在新的 POI 版本上行为一致,得把导出结果逐项对比。这也是为什么我把这类迁移归到「中到高」成本,而不是「低」。


六、容易搞错的几个点

这几点都不复杂,但被传错的频率很高。

EasyExcel 的最后一个版本是 4.0.3,2024 年 9 月。不是 3.3.2。3.3.2 是 2023 年 5 月的东西,它后面还有 3.3.3、3.3.4,最后才是 4.0.3。4.0.3 这次升级的重点之一就是 JDK 21 支持,所以只看到 3.3.2,会漏掉归档前最关键的一次变化。

协议方面,EasyExcel、FastExcel、Fesod 三个都是 Apache-2.0,没有一个是 MIT。FastExcel 和 Fesod 尤其容易被当成 MIT,这两处都要注意。

入口类叫 FesodSheet,不是 Fesod;调用是 FesodSheet.write,不是 Fesod.write。这个写错代码直接编译不过。坐标是 org.apache.fesod:fesod-sheet,不存在 org.apache.fesod:fesod 这个 artifact。当前版本是 2.0.2-incubating。Fesod 是独立的 Apache 孵化项目,不是 POI 的子模块,它只是用 POI 当引擎。

这些其实都不需要靠记忆。坐标去 Maven Central 搜,入口类和包名看官方文档或源码,协议看仓库根目录的 LICENSE,版本看 release 记录,都是几分钟的事。

还有一点:迁移是两层,不是一次改包名。官方主线是 FastExcel 到 Fesod,EasyExcel 用户得先过 FastExcel 这一层。

最后一个人名容易混。FastExcel 是庄家钜主导的,他原本就是 EasyExcel 的维护者;而 EasyExcel 最初的作者是姬朋飞。这是两个人。把这两者当成同一个人,会让人以为 FastExcel 是原作者的回归之作,其实它是维护者在项目停滞后接着往下做的一条 API 兼容的延续线。

这些点单看都是小地方,但选型的时候会被放大:版本号看错,可能让你以为项目早就死了;协议看错,合规评估就建在错误前提上;坐标和入口类看错,代码连编译都过不了。


七、我们自己的项目,迁还是不迁

背景得先交代,否则结论没意义。我们的技术栈是 Spring Boot 3.2 加 JDK 21,多模块 Maven 单体,Excel 用的是 EasyExcel 4.0.3,版本在根 POM 里统一管。

真正碰 Excel 的代码不多:4 个 Java 文件,大约 600 行,集中在 2 个应用里。除此之外整个代码库没有别的地方碰 Excel。这个规模不大,但也不是随手就能推平。代码量小意味着盘点快,难度集中意味着真正要花时间的就那一个文件。

这 4 个文件里难度分布很不均。有一个大概 400 行的样式导出器是硬骨头:它嵌了 logo 图片,做了单元格合并,设了自定义行高,逐单元格配了样式,还配了 A4 打印,页脚页码加重复表头行。它同时用了 EasyExcel 的 handler 扩展点和原生 POI 类。剩下的很轻,一个模板下载,加一个带单个 @ExcelProperty DTO 的 doReadSync 导入,基本是机械活。

我们最后决定不迁,理由三条:

  • 4.0.3 已经在 JDK 21 / POI 5.2.5 上正常工作,没有影响我们的 CVE;
  • 归档的库不等于有漏洞的库,它只是不再有新功能,而我们不需要新功能;
  • Fesod 还在孵化期,IP 清理尚在进行,而那个 400 行的样式导出器迁过去等于重写加重新验证,功能上没有任何收益。

这三条里,第三条是关键。前两条是防守,第三条是实打实的成本。那个导出器里,图片、合并、行高、样式、打印设置和 POI 原生调用是混在一起的,换底层引擎意味着这些行为都要重新验一遍。验证这种代码,靠跑一遍导出看一眼是不够的,得逐项对照格式和打印效果。

真要迁,工作量大致是这样:把 handler 和 POI 调用逐个对照新版本改一遍,再拿真实数据跑导出,逐项检查图片位置、合并范围、行高、单元格样式和打印分页。这些检查没有捷径,因为 POI 版本变了,同样的调用行为可能有细微差别。

说到底,迁移在我们这里的投入产出比是负的:付出一个复杂导出器的重写成本,换来的是一个名字更正统的依赖。

那什么时候才值得迁?我们给自己列了触发条件,满足任意一条再动手:

  • 有真正可利用的漏洞落到 EasyExcel 4.0.3 上;
  • 确实需要 Fesod 独有的能力,比如 Excel 转 PDF、读取指定行区间这类 EasyExcel 做不到的功能;
  • 团队统一要求依赖必须挂在 Apache 基金会名下,合规或采购层面的硬性要求。

在这三条之外,为了追热度去迁一个已经稳定的导出模块,不划算。

除此之外我们没做别的动作。EasyExcel 4.0.3 继续用,版本锁在根 POM 里,依赖扫描照常跑。哪天扫描真的报出一个可利用的漏洞,再按第一条触发迁移,那时候该评估什么、改哪里,也已经清楚了。


八、写在最后

这轮变化里最容易被混在一起的是两件事:毕业和停更。

EasyExcel 是停更,代码冻结,不再有新功能,但现有版本仍然能用。FastExcel 和 Fesod 是毕业,项目从个人维护走向基金会治理,获得了更长期的生存保障。

这两件事方向完全不同,但对使用者的意义经常被搅在一起。一个稳定的依赖停更,对绝大多数项目不构成风险;一个正在孵化的项目毕业,也不代表你必须跟过去。选型是把你的风险和收益对上,不是谁新就跟谁。如果你的 Excel 需求已经稳定,底层库又没有实际风险,那不动往往就是最省事也最稳的选择。

能用不等于该换。

先把自己项目里的 Excel 代码盘清楚,再决定要不要动。多数时候你会发现,真正要改的就是那个最复杂的文件,而它恰恰是最不该为了「新」而重写的。