Java POI 模板生成 Word 文档:占位符替换与循环表格实战
简介这份资源面向需要在Java环境中批量生成定制化Word文档的开发者聚焦于用Apache POI读取预设模板、替换占位符并输出个性化文档这一典型场景适合已具备一定Java基础、希望将报表、合同等文档生成流程自动化的中高级程序员参考。压缩包共11个文件约7.37MB包含5个jar依赖包poi、poi-ooxml、xmlbeans、dom4j等核心库、3个java源码文件如WordUtil、CustomXWPFDocument、SimpleDocument等工具类、2个docx模板示例以及1张效果图覆盖从依赖引入到模板读写的完整链路。目前已有4138人学习下载说明该方案在实际项目中具有较高参考价值。读者可借助其中的工具类与模板样例快速理解XWPFDocument、XWPFParagraph、XWPFTable等对象的操作方式掌握段落文本替换、表格数据填充及文件写出关闭等关键环节并在此基础上扩展图片处理、动态数据映射与异常处理逻辑从而搭建起可复用的Word文档生成模块。1. 模板引擎选型为什么 POI 仍是 Java 生成 Word 的稳妥底牌做过企业级文档导出的人多半有过这种经历需求方丢来一份排版精美的 Word 模板要求把数据库里的字段填进去保留页眉页脚、表格样式、字体字号还要支持批量生成。第一反应可能是用 POI 从零画表格、设样式结果写了三百行代码导出的文档还是歪的。更省力的路径是「模板 占位符替换」——让设计人员在 Word 里把版式调好程序只负责把数据塞进指定位置。Java 生态里做这件事Apache POI 配合 XWPF 组件是最常见也最稳的组合它不依赖 Office 环境纯 Java 操作 .docx跨平台部署没有额外负担。这篇笔记就围绕「java poi 通过模板生成 word 文档」这条主线把选型理由、占位符设计、段落与表格替换、循环行处理、以及那些让人加班到深夜的坑一条条拆开讲清楚。适合已经会用 Java 读写文件、但被 Word 排版折磨过的后端开发者。2. 用 XWPF 打开模板并替换占位符最小可跑通的骨架2.1 依赖引入与模板文件放置先把依赖理清楚。POI 操作 .docx 需要poi-ooxml它内部会带上poi和poi-ooxml-lite。版本上不必追最新选一个团队里已经在用的稳定版即可避免和现有依赖树冲突。Maven 里加这么一段dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version /dependency模板文件放在src/main/resources/templates/下用类路径加载这样打成 jar 后依然能读到。注意模板必须是.docx.doc是二进制格式XWPF 处理不了这是第一个容易翻车的地方。2.2 读取模板与段落文本替换最小骨架就三步打开模板、遍历段落、替换占位符。占位符建议用{{fieldName}}这种双花括号形式比${}更不容易和 Word 域代码冲突。import org.apache.poi.xwpf.usermodel.*; import java.io.*; import java.util.Map; public class WordTemplateWriter { public static void write(String templatePath, String outputPath, MapString, String data) throws IOException { try (InputStream is WordTemplateWriter.class .getResourceAsStream(templatePath); XWPFDocument doc new XWPFDocument(is); FileOutputStream fos new FileOutputStream(outputPath)) { // 1. 替换段落中的占位符 for (XWPFParagraph paragraph : doc.getParagraphs()) { replaceInParagraph(paragraph, data); } // 2. 替换表格中的占位符 for (XWPFTable table : doc.getTables()) { for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { for (XWPFParagraph p : cell.getParagraphs()) { replaceInParagraph(p, data); } } } } doc.write(fos); } } private static void replaceInParagraph(XWPFParagraph paragraph, MapString, String data) { // 先拼接整段文本再统一替换避免 run 被拆散 StringBuilder full new StringBuilder(); for (XWPFRun run : paragraph.getRuns()) { full.append(run.text()); } String replaced full.toString(); for (Map.EntryString, String e : data.entrySet()) { replaced replaced.replace({{ e.getKey() }}, e.getValue()); } if (!replaced.equals(full.toString())) { // 清空原有 run把替换后的文本写回第一个 run for (int i paragraph.getRuns().size() - 1; i 0; i--) { paragraph.removeRun(i); } XWPFRun newRun paragraph.createRun(); newRun.setText(replaced); } } }这段代码的关键在replaceInParagraph。Word 会把一段文字拆成多个 run比如「{{name}}」可能被拆成「{{」「name」「}}」三个 run直接对单个 run 做 replace 会漏掉。所以先把整段 run 的文本拼起来替换完再清空重写。代价是段落内原有的局部样式比如某个词加粗会丢失如果模板里同一段有混合样式需要更细的 run 级处理后面避坑章节会讲。参数说明templatePath是类路径下的相对路径data的 key 不带花括号value 为 null 时建议替换成空字符串而不是字面量「null」否则文档里会出现尴尬的 null 字样。2.3 表格单元格替换的注意点表格替换和段落逻辑一样但要注意单元格里可能有多个段落每个段落都要走一遍替换。另外合并单元格在 POI 里表现为多个 cell 引用同一块区域遍历时可能重复替换不过因为替换是幂等的占位符已经没了重复执行不会出错。真正要小心的是单元格里嵌套表格getTables()只返回顶层表格嵌套表格需要递归取cell.getTables()这个在复杂模板里很常见。3. 循环行与动态表格把 List 数据铺进 Word 表格3.1 循环行的占位符约定静态字段替换只是开胃菜真正难的是动态行——比如订单明细有 N 条模板里只画了一行需要根据数据量复制出 N 行。常见做法是在模板表格里放一行「模板行」用{{item.name}}、{{item.price}}这类带前缀的占位符标记程序识别到这一行含循环占位符后按数据量克隆行并逐行替换。private static void fillTableRows(XWPFTable table, String prefix, ListMapString, String rows) { // 找到含 {{prefix. 的模板行下标 int templateIdx -1; for (int i 0; i table.getRows().size(); i) { if (rowContains(table.getRow(i), {{ prefix .)) { templateIdx i; break; } } if (templateIdx 0 || rows.isEmpty()) return; XWPFTableRow templateRow table.getRow(templateIdx); // 从后往前插入避免下标错乱 for (int i rows.size() - 1; i 0; i--) { XWPFTableRow newRow table.insertNewTableRow(templateIdx 1); copyRow(templateRow, newRow); replaceRow(newRow, prefix, rows.get(i)); } // 删除模板行本身 table.removeRow(templateIdx); }逻辑说明先定位模板行然后按数据条数克隆。克隆用insertNewTableRow在模板行下方插入复制时要把模板行每个 cell 的段落和 run 样式一并复制过去否则新行会丢样式。替换完成后删掉模板行避免文档里残留一行占位符。参数prefix用来区分同一文档里多组循环数据比如item和fee互不干扰。3.2 复制行样式的实现细节copyRow是这段逻辑里最容易写糙的地方。POI 没有现成的行克隆 API需要手动复制 cell 的宽度、段落的对齐方式、run 的字体和加粗状态。private static void copyRow(XWPFTableRow src, XWPFTableRow dest) { dest.setHeight(src.getHeight()); for (int i 0; i src.getTableCells().size(); i) { XWPFTableCell srcCell src.getCell(i); XWPFTableCell destCell dest.getCell(i); // 清空目标 cell 默认段落 destCell.removeParagraph(0); for (XWPFParagraph srcP : srcCell.getParagraphs()) { XWPFParagraph destP destCell.addParagraph(); destP.setAlignment(srcP.getAlignment()); for (XWPFRun srcRun : srcP.getRuns()) { XWPFRun destRun destP.createRun(); destRun.setText(srcRun.text()); destRun.setBold(srcRun.isBold()); destRun.setFontSize(srcRun.getFontSize()); destRun.setFontFamily(srcRun.getFontFamily()); } } } }这里只复制了常用样式属性实际项目里可能还要处理颜色、下划线、单元格底纹。如果模板样式复杂更省事的做法是让模板行本身足够简单把复杂样式放在表头数据行统一用基础样式减少复制负担。3.3 数据量过大时的分页控制当循环行超过一页时Word 会自动分页但表头不会自动重复。如果需求要求每页都带表头需要在模板里把表头行设为「重复标题行」——这是 Word 的功能POI 可以通过XWPFTableRow的底层 CTTblTr 设置tblHeader属性。另一个思路是数据量超过阈值时拆成多个文档避免单文档过大导致打开缓慢。实测单文档表格行超过两千行时Word 打开会明显卡顿建议在业务层做分批。4. 避坑与排查模板生成 Word 最常见的五类翻车4.1 占位符被 Word 拆成多个 run 导致替换失败现象模板里明明写了{{name}}程序跑完文档里还是原样或者只替换了一半。原因Word 在编辑过程中会把连续文本拆成多个 run尤其是中英文混排、复制粘贴后。解决不要对单个 run 做 replace先拼接整段文本再统一替换如第 2 章代码所示。如果段落内有混合样式必须保留可以按 run 边界做滑动窗口匹配但实现复杂度高多数场景直接整段重写更划算。4.2 替换后字体样式丢失现象替换进去的文字变成了默认宋体小五和模板里设定的字体不一致。原因清空 run 后新建的 run 没有继承原样式。解决在新建 run 之前记录第一个原 run 的字体、字号、加粗状态赋给新 run。或者更稳妥的做法是保留第一个 run只改它的文本删除其余 run这样样式天然继承。4.3 表格循环行插入后样式错乱现象克隆出来的行边框消失、列宽变化、文字挤在一起。原因insertNewTableRow创建的行没有复制原行的单元格宽度和边框定义。解决复制行时显式设置 cell 宽度destCell.setWidth(srcCell.getWidth())边框则要通过底层 CTTblBorders 复制或者干脆在模板里把表格边框设为「全部框线」让新行自动继承表格级样式。4.4 中文乱码或特殊字符丢失现象导出的文档里中文变成问号或者、等字符导致文档损坏。原因POI 处理的是 XML特殊字符需要转义中文乱码通常是字体问题而非编码问题。解决替换值里的、、用run.setText(text, 0)时 POI 会自动处理转义但如果手动拼 XML 就要自己转。中文乱码检查模板默认字体是否设置了中文字体如宋体、微软雅黑纯英文模板插入中文可能显示异常。4.5 模板文件被占用或输出流未关闭现象第二次生成时报「文件被另一个进程占用」或者生成的文档打不开。原因XWPFDocument和FileOutputStream没有正确关闭Windows 下文件句柄未释放。解决用 try-with-resources 包裹所有流确保doc.close()和fos.close()都被调用。如果输出文件已存在且被 Word 打开写入会失败业务层要捕获 IOException 并给出明确提示。5. 进阶技巧用 XWPFDocument 做条件段落与图片插入模板生成走到后面需求往往会升级某些段落要根据条件显示或隐藏文档里还要插入动态图片。这两件事 POI 都能做但姿势要对。条件段落的核心思路是「标记 删除」。在模板里给可选段落加上{{#if flag}}和{{/if}}标记程序遍历段落时识别标记区间flag 为 false 就删除区间内所有段落。删除段落用doc.removeBodyElement(doc.getPosOfParagraph(paragraph))注意删除时下标会变建议先收集要删的段落对象再统一删。private static void removeParagraph(XWPFDocument doc, XWPFParagraph p) { int pos doc.getPosOfParagraph(p); if (pos 0) { doc.removeBodyElement(pos); } }图片插入用paragraph.createRun().addPicture(is, XWPFDocument.PICTURE_TYPE_PNG, img.png, Units.toEMU(120), Units.toEMU(80))。参数依次是输入流、图片类型、文件名、宽、高单位是 EMUUnits.toEMU把像素转成 EMU。图片流记得单独关闭否则会泄漏。如果图片要放在表格单元格里先拿到 cell 的段落再 createRun。一个我踩过的坑在循环行里插图片时如果图片流被多个 run 复用第二个 run 会读到空流。解决办法是每次插入都重新打开一次输入流或者把图片字节缓存到 byte 数组用new ByteArrayInputStream(bytes)反复构造。验证生成结果是否正确的习惯不要只用 Word 打开肉眼看写个单元测试用 POI 读回文档断言关键字段的文本和表格行数。这样回归时能自动发现模板改动导致的替换失效。我一般会在测试里覆盖「空数据」「单条数据」「超过一页数据」三种情况基本能拦住大部分低级错误。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

仿贝壳房产系统实战:房源数据模型与筛选性能优化

仿贝壳房产系统实战:房源数据模型与筛选性能优化

简介:这是一套面向房产中介创业者、房产门户运营方及二次开发者的开源房产系统网站源码,主打仿贝壳、链家、58同城等平台的业务模式,可一站式搭建新房、二手房、出租房、小区、问答等多场景房产电商平台。系统同时覆盖PC端与手机端&#xff0…

2026/10/11 21:14:03 阅读更多 →
HDFS存储优化实战:纠删码、压缩与小文件治理策略

HDFS存储优化实战:纠删码、压缩与小文件治理策略

大数据项目的存储层里,HDFS 通常是最先被塞满、却最后一个被优化的组件。大多数团队在容量告警触发之前,并不会认真考虑副本数、文件格式、冷数据沉降这些事,等磁盘真的快满了,第一反应往往是再加节点。这篇文章是我在生产环境里做…

2026/10/11 21:13:02 阅读更多 →
Debian 12下FFmpeg安装全攻略:apt源、静态构建与源码编译

Debian 12下FFmpeg安装全攻略:apt源、静态构建与源码编译

1. 项目概述:为什么要把FFmpeg安装当回事 1.1 FFmpeg到底能做什么 FFmpeg在我眼里从来不只是“一个转码工具”这么简单。它其实是一整套命令行音视频处理工具集,覆盖了视频转码、音频提取、画面裁剪、音量调整、视频拼接、等比缩放、抽帧、推流、截图、…

2026/10/11 21:13:02 阅读更多 →

最新新闻

拆解Amical的whisper.cpp封装:如何构建带Metal/CUDA/CPU自动回退的C++原生模块

拆解Amical的whisper.cpp封装:如何构建带Metal/CUDA/CPU自动回退的C++原生模块

【免费下载链接】amical 🎙️ AI Dictation App - Open Source and Local-first ⚡ Type 3x faster, no keyboard needed. 🆓 Powered by open source models, works offline, fast and accurate. 项目地址: https://gitcode.com/gh_mirrors/…

2026/10/12 0:27:12 阅读更多 →
基于YOLO的管道缺陷检测:980张图像训练实战与避坑指南

基于YOLO的管道缺陷检测:980张图像训练实战与避坑指南

简介:本资源为面向YOLO系列目标检测算法的下水管道缺陷检测数据集,适用于从事管道巡检、市政设施维护与工业视觉检测的开发者及研究人员,可解决缺陷样本稀缺、标注格式不统一等问题。压缩包共2000个文件,约33.89MB,包含…

2026/10/12 0:27:12 阅读更多 →
物联网模组柔性FPC天线方案全解析:选型、布局与调试

物联网模组柔性FPC天线方案全解析:选型、布局与调试

1. 项目背景与选型思路做物联网产品硬件设计的朋友,十有八九都遇到过同一个问题:模组选好了、主板画完了、结构堆叠也敲定了,结果天线没地方放。尤其是这两年,NB-IoT、Cat.1、BLE、LoRa 这些模组方案层出不穷,模组本身…

2026/10/12 0:27:12 阅读更多 →
用Tauri构建桌面天气应用:从技术选型到打包发布的完整实践

用Tauri构建桌面天气应用:从技术选型到打包发布的完整实践

桌面天气应用这个需求,看起来挺简单,但真做起来会发现它横跨了数据接口、桌面端集成、界面设计、异常处理好几个层面的问题。我前后用了两个周末把一套完整方案跑通,过程中踩了不少坑,这里把从选型到发布的完整链路梳理出来&#…

2026/10/12 0:27:12 阅读更多 →
UML四层建模实战:从用例图到部署图构建教务管理系统

UML四层建模实战:从用例图到部署图构建教务管理系统

简介:本资源是南京邮电大学软件工程课程设计的完整实验报告,面向高校计算机类专业本科生及软件工程初学者,聚焦教务管理系统的面向对象分析与UML建模实践。报告系统呈现了从需求分析到UML建模的全流程:涵盖用例图(管理…

2026/10/12 0:26:12 阅读更多 →
UML用例图与顺序图建模核心:抓准动作主体与交互时序

UML用例图与顺序图建模核心:抓准动作主体与交互时序

简介:本资源是一份面向软件工程专业学生、UML初学者及备考人员的系统性试题汇编,聚焦用例图、顺序图与协作图等核心交互建模技能,帮助读者深入理解UML动态建模原理与实际应用差异。资料以1个62KB的Word文档形式呈现,内容涵盖7大知…

2026/10/12 0:26:12 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →