
Apache POI 3.17 在生成 .xlsx 文件时会错误设置工作表维度(),导致 MS Access 等严格解析 XML 结构的工具识别出一个不存在的空白列;该缺陷已在 4.0.0+ 版本中修复,根本解决方式是升级至最新稳定版(如 5.2.3)。
apache poi 3.17 在生成 `.xlsx` 文件时会错误设置工作表维度(`
在使用 Apache POI 3.17 创建 Excel 文件时,虽然 Excel 应用程序能正常渲染内容(如两列 "TEST1" 和 "TEST2"),但当该文件被 MS Access、Power Query 或其他基于 OpenXML 规范严格校验 sheet.xml 的工具打开时,会意外识别出第三列(即 C1 单元格),表现为末尾多出一个空列。这一现象并非数据写入错误,而是底层 XML 元数据中的 <dimension></dimension> 标签值被错误计算所致。
? 根本原因:<dimension></dimension> 值计算偏差
.xlsx 是 ZIP 压缩包,解压后可查看 xl/worksheets/sheet1.xml。其中 <dimension ref="A1:B1"></dimension> 表示有效数据区域为第 1 行、A 列到 B 列(共 2 列)。而 POI 3.17 中 XSSFSheet 的实现存在逻辑缺陷:
// Apache POI 3.17 源码片段(XSSFSheet.java 第 3501 行附近) maxCell = Math.max(maxCell, row.getLastCellNum()); // ❌ 错误:getLastCellNum() 返回的是「最后一个有值单元格的列索引 + 1」 // 若只创建了索引 0 和 1 的单元格,则 getLastCellNum() = 2 → maxCell = 2 → dimension 变为 "A1:C1"
而自 POI 4.0.0 起已修正:
// Apache POI 4.0.0+ 正确写法 maxCell = Math.max(maxCell, row.getLastCellNum() - 1); // ✅ 减 1 后得到真实最大列索引
因此,3.17 生成的 XML 片段为:
<dimension ref="A1:C1"></dimension>
而 5.2.3 生成的是:
<dimension ref="A1:B1"></dimension>
——后者才准确反映实际数据范围。
⚠️ 不推荐的“临时方案”
-
手动修改 XML:在
workbook.write()前通过反射或 XML 解析器篡改sheet1.xml中的<dimension></dimension>值?不可行。因为XSSFSheet.write()内部会重新生成并覆盖所有 XML 元素。 - 降级版本:回退到 3.11 或 3.7 虽可规避此问题,但将失去后续所有安全补丁、性能优化及新特性支持(如 XSSF 支持公式重计算、图表增强等),且旧版本本身存在已知 CVE 漏洞。
-
调用
row.removeCell(...):如问题中尝试的row.removeCell(row.getCell(2))会抛NullPointerException,因为getCell(2)返回null(该列未创建),无法移除不存在的对象。
✅ 唯一可靠方案:升级至最新稳定版
截至 2024 年,Apache POI 最新稳定版为 5.2.4(Maven 坐标):
<dependency><groupid>org.apache.poi</groupid><artifactid>poi-ooxml</artifactid><version>5.2.4</version></dependency>
升级后,原示例代码无需任何修改即可正确输出:
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Data");
XSSFRow row = sheet.createRow(0);
row.createCell(0).setCellValue("TEST1");
row.createCell(1).setCellValue("TEST2");
try (FileOutputStream out = new FileOutputStream("test.xlsx")) {
workbook.write(out); // ✅ 自动生成 <dimension ref="A1:B1"></dimension>
}
? 额外建议:
- 使用 try-with-resources 确保
Workbook和OutputStream自动关闭(如上所示),避免资源泄漏;- 避免重复调用
workbook.write(...)或手动操作ByteArrayOutputStream后再写文件(原代码中baos未定义且存在冗余写入);- 若项目受限于老旧 JDK(如 JDK 8),POI 5.x 仍完全兼容,无需升级 JVM。
总结:该问题是典型的历史版本 Bug,非业务逻辑缺陷。Apache POI 官方明确不维护旧版本的 bug 修复,坚持使用最新版是保障兼容性、安全性与可维护性的最佳实践。











