
Apache POI 3.17 版本存在 XSSFSheet 维度计算缺陷,导致生成的 .xlsx 文件中 错误地多包含一列(如仅写入2列却标记为3列),MS Access 等严格解析 XML 的工具会据此创建空列;该问题已在 POI 4.0.0+ 版本修复。
apache poi 3.17 版本存在 `xssfsheet` 维度计算缺陷,导致生成的 `.xlsx` 文件中 `
在使用 Apache POI 生成 Excel 文件时,若目标系统(如 Microsoft Access、某些 ETL 工具或数据导入服务)对 .xlsx 的底层 XML 结构敏感,就可能暴露出版本兼容性问题。典型现象是:Excel 应用程序中显示正常(列数无误),但 MS Access 导入时自动识别出额外的空白列(例如写入 TEST1、TEST2 两列,却出现第三列 Column3: <null></null>)。这一问题专属于 Apache POI 3.17,在 3.11、3.7、4.x 及 5.x 各版本中均未复现——根本原因在于该版本对工作表 <dimension></dimension> 标签的计算逻辑存在 Bug。
? 问题本质:<dimension></dimension> 标签被错误扩展
.xlsx 是 ZIP 压缩包,解压后路径 /xl/worksheets/sheet1.xml 中的 <dimension ref="..."></dimension> 元素定义了工作表实际使用的单元格范围(如 A1:B1 表示第1行、第1–2列)。POI 3.17 的 XSSFSheet 实现中,关键代码如下(源码第3501行):
maxCell = Math.max(maxCell, row.getLastCellNum()); // ❌ 错误:getLastCellNum() 返回的是「最后一个有值列索引 + 1」
Row.getLastCellNum() 的语义是 “最后一个非空单元格的列索引 + 1”(基于 0 起始索引)。例如,你在第0行设置了 cell(0) 和 cell(1),则 getLastCellNum() 返回 2(即列 B 的下一个位置)。但 POI 3.17 直接将 2 当作最大列号参与维度计算,最终生成:
<dimension ref="A1:C1"></dimension><!-- 实际只用了 A1:B1,C1 是不存在的 -->
而从 POI 4.0.0 起已修正为:
maxCell = Math.max(maxCell, row.getLastCellNum() - 1); // ✅ 正确:减1后得到真实最大列索引(B 列对应索引 1)
因此输出为符合预期的:
<dimension ref="A1:B1"></dimension>
✅ 推荐解决方案:升级至最新稳定版
最可靠、最可持续的方式是升级 Apache POI 至当前最新稳定版(截至 2024 年为 5.2.4 或 5.3.0)。无需修改业务逻辑,仅需更新依赖:
Maven(pom.xml):
<dependency><groupid>org.apache.poi</groupid><artifactid>poi-ooxml</artifactid><version>5.2.4</version><!-- 或更高 --></dependency>
同时,优化您原始代码中的资源管理与潜在风险点(如未初始化 baos、重复写入、close() 顺序等):
public static String createExcel(String test) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Data");
XSSFRow row = sheet.createRow(0);
row.createCell(0).setCellValue("TEST1");
row.createCell(1).setCellValue("TEST2");
// ✅ 正确写入:一次写入到 baos
workbook.write(baos);
byte[] bytes = baos.toByteArray();
File file = new File("C:\Files\test.xlsx");
try (FileOutputStream out = new FileOutputStream(file)) {
out.write(bytes);
}
String encodedBase64 = Base64.getEncoder().encodeToString(bytes).trim();
System.out.println("encodedBase64: " + encodedBase64);
return encodedBase64;
} catch (Exception e) {
System.err.println("Excel generation failed: " + e.getMessage());
throw new RuntimeException(e);
}
}
⚠️ 注意事项:
- 切勿降级或锁定旧版本:POI 3.17 已于 2017 年停止维护,除该维度 Bug 外,还存在安全漏洞(如 CVE-2018-8010)、XSS 漏洞及缺失新 Excel 功能支持;
- 避免手动修补源码:自行修改
XSSFSheet.java并重编译不仅增加维护成本,且无法保证其他模块兼容性;- 验证导出文件结构:可将生成的
.xlsx文件改名为.zip,解压后检查/xl/worksheets/sheet1.xml中的<dimension ref="..."></dimension>是否与实际数据范围一致。
? 总结
Apache POI 3.17 的「多列」问题并非数据写入错误,而是元数据(<dimension></dimension>)计算偏差所致,根源明确、修复成熟。坚持使用官方最新版 POI,既是规避已知缺陷的最佳实践,也是保障长期可维护性与安全性的技术底线。升级后,您将获得更精准的 Excel 结构控制、更强的格式兼容性,以及持续的安全更新支持。











