Java POI 3.17写入xlsx时出现OpenXML4JRuntimeException怎么解决?
问题复现
使用Java POI 3.17 API向新建xlsx文件写入内容时,单元测试执行正常,系统测试环节触发如下报错:
org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException: Fail to save: an error occurs while saving the package : The part /docProps/core.xml failed to be saved in the stream with marshaller org.apache.poi.openxml4j.opc.internal.marshallers.ZipPackagePropertiesMarshaller@4ae061e at org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:595) at org.apache.poi.openxml4j.opc.OPCPackage.save(OPCPackage.java:1539) at org.apache.poi.POIXMLDocument.write(POIXMLDocument.java:227)
错误产生原因
- JAXB依赖冲突:是该场景下最高发的原因。单元测试环境依赖树干净,没有额外引入JAXB相关组件,而系统测试环境大概率引入了不兼容版本的JAXB实现:比如JDK 9+自带的高版本JAXB与POI 3.17依赖的JAXB 2.2.x版本不兼容,或者项目中其他组件(如CXF、Spring OXM)引入了冲突版本的JAXB,最终导致core.xml序列化失败。
- 临时目录权限不足:POI写入xlsx时会在系统临时目录生成临时文件,单元测试用本地用户权限执行,对临时目录有读写权限;但系统测试环境的运行账号(如Linux下的服务运行账号、容器内的非root账号)没有
java.io.tmpdir对应目录的读写权限,无法完成临时文件的序列化写入。 - 输出流提前关闭/被占用:系统测试的代码链路比单元测试长,可能存在过滤器、拦截器或者其他业务逻辑,在POI执行
write操作前就关闭了目标输出流,或者输出流已经被写入了无关数据导致格式损坏。 - 文档属性包含非法字符:如果代码中设置了xlsx的文档属性(作者、标题、备注等),单元测试用的测试值都是合法字符,而系统测试时传入了XML非法字符(如\0、\u0001等控制字符),会导致core.xml序列化报错。
修复方案
- 解决JAXB依赖冲突
- 在项目依赖中显式锁定POI 3.17配套的JAXB版本,排除其他依赖引入的高版本JAXB,Maven参考配置如下:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <!-- 锁定JAXB版本适配POI 3.17 --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.12</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.12</version> </dependency>
- 如果运行环境是JDK 9+,需要额外添加JVM启动参数
--add-modules java.xml.bind,或者直接把JAXB相关依赖打包进应用,避免使用JDK自带的高版本JAXB。
- 解决临时目录权限问题:检查系统测试环境运行账号对
java.io.tmpdir指定目录的读写权限,也可以在启动参数中指定有权限的临时目录:-Djava.io.tmpdir=/path/to/writable/dir - 解决输出流异常:排查系统测试链路中输出流的操作逻辑,确保POI的
write方法执行完成前输出流不会被关闭,且没有其他逻辑向同一个输出流写入无关数据。 - 解决非法字符问题:对设置的文档属性值做XML非法字符过滤,移除所有ASCII控制字符(除换行、制表、回车),参考实现代码:
public static String filterXmlInvalidChar(String input) { if (input == null) return ""; return input.replaceAll("[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]", ""); }
内容的提问来源于stack exchange,提问作者Jen_H
相关产品推荐
相关产品推荐

