javapoi多个word合并:从准备到排错的完整做法

手头有一批 Word 文档,需要合成一份交出去,又不想一个个复制粘贴——用 Java 配合 Apache POI 处理是常见思路。但真写起来,很多人卡在依赖版本对不上、样式互相覆盖、页眉页脚丢失这些地方。这篇教程面向已经会写 Java、准备在项目里做文档合并的读者,从准备工作讲到出错排查,把容易忽略的细节说清楚。

先把依赖和文档格式确认好

动手写代码之前,有两件事要先定下来。

第一是依赖。Apache POI 处理 Word 主要用 XWPF 这套 API,对应的是 .docx 格式。如果你手上的文件是老的 .doc,XWPF 读不了,得先转成 .docx,或者换用 HWPF 那套接口,但两套接口的写法差别不小,中途混用容易出问题。Maven 里引入 poi-ooxml 即可,注意它和 poi 主包的版本号要一致,版本错配时经常在运行期才抛出类找不到的异常,编译阶段看不出来。

第二是文档结构。合并前先抽样打开几份源文件,看看有没有分节符、页眉页脚、脚注、批注、图片、表格。这些元素在合并时处理方式不同:普通段落和表格可以直接搬;页眉页脚、分节信息属于文档级设置,简单搬运会丢失或串位;图片如果是外部链接引用,换台机器就可能显示不出来。心里有数,后面出问题才知道往哪儿查。

另外提醒一句:合并后的目标文档最好新建一个空的 XWPFDocument 作为容器,而不是拿其中一份源文件当底稿往上追加,后者容易把底稿的页面设置带进整份结果里。

按段落逐个搬,而不是整篇塞进去

很多人第一反应是找“把两个 XWPFDocument 拼起来”的方法,实际上没有现成的整篇合并接口。可行的做法是遍历源文档的正文元素,逐个复制到目标文档里。

顺序上分几步:先创建目标文档对象;然后对每个源文件用 FileInputStream 打开,构造 XWPFDocument;接着遍历它的段落和表格;每处理完一份就关掉输入流,避免文件句柄堆积。

复制段落时,新建段落对象后要逐项设置对齐方式、行距、缩进这些段落属性,再遍历段内的 run,把文字、字体、字号、加粗斜体、颜色一并复制过去。只复制文字不复制 run 属性,结果就是所有格式被抹平,读起来像纯文本。

表格稍微麻烦:先按源表的行列数在目标文档里建表,再逐格填内容,单元格里的段落同样要按 run 复制。表头重复、合并单元格这些属性需要单独判断,漏掉的话表格结构会变形。

如果源文档里有图片,不能只复制段落文字,还要把图片数据取出来重新插入,否则段落里只剩一个空占位。这一步建议单独写个方法处理,别和文字复制混在一起。

样式冲突与页眉页脚的取舍

合并后最常见的抱怨是“格式全乱了”。原因通常不在复制逻辑,而在样式定义。每份源文档都有自己的样式表,同名样式(比如都叫“标题 1”)在不同文件里可能字号、间距完全不同。直接搬段落时,目标文档里没有对应样式定义,Word 会回退到默认样式,看起来就变了样。

处理办法有两种思路。一是合并前统一源文档的样式模板,让它们基于同一套样式定义,这样搬运后基本一致;二是接受样式差异,在复制 run 时把字体、字号这些直接属性写死,不依赖样式名。前者规范但要求源头可控,后者省事但后期改格式麻烦,按实际场景选。

页眉页脚要单独对待。它们挂在节(section)上,不属于正文段落,遍历正文时根本碰不到。如果业务上要求合并后保留各段来源的页眉,就得在插入每份文档内容前新建分节,再把该文档的页眉页脚内容复制到新节里;如果只要求统一页眉,那就在目标文档里设置一次,合并时忽略源文档的页眉页脚即可。先想清楚要哪种,再决定代码怎么写,别等合并完发现页眉没了才回头改。

跑不通时先查这几处

代码写完跑出异常,别急着改逻辑,按下面几处排查往往更快。

报 ClassNotFoundException 或 NoSuchMethodError,基本是依赖版本不一致,检查 poi 与 poi-ooxml 是否同版本,以及项目里有没有其他库间接引入了旧版 POI。

打开文件时报 InvalidFormatException,多半是文件扩展名和实际格式不符,比如把 .doc 改名成 .docx。用压缩工具打开看看内部结构是不是 OOXML 那套目录,能快速确认。

合并结果里文字在、格式没了,回到 run 复制那一步,确认是否只调了 setText 而没设置字体属性。

文件被占用打不开,检查输入流是否在 finally 里关闭,以及输出流是否在写完内容后才关闭——顺序反了会得到空文件或损坏文件。

还有一种隐蔽情况:源文档里含有宏或特殊控件,XWPF 读取时可能抛异常。这类文档建议先在 Word 里另存为普通文档再参与合并。

让合并逻辑更稳的几个习惯

把合并封装成一个接收文件列表、返回目标文档的方法,调用方只管传路径,内部处理打开、复制、关闭。这样以后换实现方式,调用方不用动。

复制逻辑按元素类型拆成小方法:复制段落、复制表格、复制图片各管各的,主流程只负责遍历和分发。元素类型判断用 instanceof 分支,遇到不认识的类型跳过并记日志,别让整批任务因为一个特殊元素中断。

合并大量文件时注意内存,不要把所有源文档对象同时打开,处理完一份立刻关闭释放。输出前可以先用少量文件试跑,确认格式和结构符合预期,再扩大到全量。

最后留个心眼:合并结果交给别人打开前,自己先用 Word 过一遍,重点看目录、页码、表格边框这些容易出问题的地方。程序跑通和文档能正常阅读,中间还差一次人工检查。

总结

用 Apache POI 做 javapoi多个word合并,核心是新建目标文档、逐份打开源文件、按段落和表格逐个复制并保留 run 格式,最后统一处理页眉页脚和样式。跑不通时优先查依赖版本、文件格式和流的关闭顺序,合并结果再人工过一遍。

相关文章

常见问题

Q1为什么合并后页眉页脚不见了?

页眉页脚挂在节上,不属于正文段落,遍历正文时不会被复制。需要在插入每份文档内容前新建分节,再单独复制页眉页脚内容;或者干脆在目标文档统一设置一次,合并时忽略源文档的页眉页脚。

Q2合并后字体和字号全变了怎么办?

多半是只复制了文字没复制 run 属性,或者源文档同名样式定义不同导致回退到默认样式。检查复制逻辑是否逐项设置了字体、字号等属性,或者合并前统一各源文档的样式模板。

Q3能不能直接合并 .doc 文件?

XWPF 这套 API 只处理 .docx。老的 .doc 需要先转成 .docx 再参与合并,否则读取阶段就会报格式异常。转完记得确认文件内部结构确实是 OOXML 格式。

Q4运行时报类找不到或方法不存在是什么原因?

通常是 POI 相关依赖版本不一致,poi 与 poi-ooxml 版本号要相同;也可能是项目里其他库间接引入了旧版 POI。排查依赖树,把冲突的版本排除掉。

Q5合并后的文件打不开或者内容为空?

检查输出流和输入流的关闭顺序,输出流要在内容全部写入后再关闭;同时确认每份源文档处理完就关闭,别让句柄堆积。文件被占用时也会导致写入不完整。