拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java导出Word文档实战:基于Apache POI生成docx全指南

简介一套基于Java与Apache POI的Word文档导出可运行源码包面向需要在Java应用中生成报告文件的后台开发工程师。资源围绕使用POI完成包含图片、表格及格式化文本的Word文档构建完整覆盖Maven依赖引入、XWPFDocument创建、图片二进制流插入、字体与段落设置、表格创建及样式调整等核心环节并附带最终效果与导入运行指南可帮助开发者直接套用或改造用于报告生成、数据导出等场景。包内共14个文件包含4个java源码、3个md说明文档、2个xml工程配置另有xlsx示例数据、png效果图及docx生成样例整体压缩包仅25KB结构简洁便于快速阅读与调试。目前已有42人学习下载对于希望快速掌握Java导出Word实现细节的中级开发者是一份轻量且实用的参考资料。1. 项目概述与方案选型1.1 需求背景为什么业务部门总爱要Word最近在给公司做月度报表功能时接到一个特别典型的需求把数据库里的统计数据导成Word文档交付给业务部门走审批流程。这种“Java导出Word文档”的需求在后台系统里实在太常见了导出合同、导出简历、导出实验报告、导出会议纪要全是同一套套路。为什么大家不直接要Excel或者PDF我个人的理解是Excel太“原始”用户拿过去还要自己调格式PDF虽然排版固定但不能改字审批流程里经常要临时划掉一两个字再签字只有Word既能打开直接编辑又符合正式文件的调性。所以后台系统里只要涉及“给人看的正式文档”需求大概率会落到Word上。今天分享的这套“可运行源码”基于Apache POI实现纯Java生成.docx文件不需要本机安装Office部署在Linux服务器上一样能流畅跑。内容从环境搭建、代码实现到各类坑位排查都会讲到适合刚学完Java基础、想找个实战项目练手的同学也适合正在被文档导出需求折磨得焦头烂额的在职开发者。1.2 方案对比为什么我选了POI而不是模板引擎在Java生态里做Word导出粗略分有三条主流路线Apache POI直接操作Word的XML结构自由度高适合内容动态变化、结构不固定的场景。FreeMarker Word模板提前做好模板用占位符填充数据适合固定格式的批量文档。poi-tl基于POI封装的模板引擎语法更友好适合“固定模板 少量数据”的报表类需求。我最终选择原生POI理由很直白需求里的文档内容是动态生成的标题层级、表格数量、图片位置都不一样用模板的话每个变化都要维护一套模板文件成本反而更高。而POI的API虽然啰嗦但每一步都看得见摸得着以后需求变动改代码比改模板更可控。另外POI本身就是一套全家桶后面如果还要导出Excel或解析PPT不需要再引额外依赖。2. 核心API与关键细节解析2.1 XWPFDocument所有内容都挂在这棵树上用POI操作docx核心入口是XWPFDocument类它对应一个完整的.docx文件。你可以把它理解成一棵大树段落、表格、图片都作为“节点”挂在这棵树上最终通过doc.write(输出流)把整棵树写进文件。这里有个重要的概念区分.docx和.doc是两种完全不同的格式。.docx本质是一个zip压缩包里面是一堆XML文件而.doc是老版本Word的二进制格式。POI对docx的支持非常成熟用XWPF系列类处理对doc只提供HWPF系列类功能简陋且多年没什么更新。所以做新项目时我强烈建议直接统一输出.docx别在doc上纠结。2.2 段落和Run样式的最小单位在XWPFDocument里一行文字对应一个XWPFParagraph但段落本身不直接存文本文本存在段落下的XWPFRun里。可以理解成段落是一个“容器”Run是容器里一段带有相同样式的文字。你在Word里看到“同一行字前面加粗、后面正常”的效果在POI里就是两个Run实现的。实际写代码时我习惯把“生成段落”的代码封装成方法因为导出文档必然有大量重复的标题、正文、说明文字。比如private static void addParagraph(XWPFDocument doc, String text, int fontSize, boolean bold, boolean center) { XWPFParagraph p doc.createParagraph(); p.setAlignment(center ? ParagraphAlignment.CENTER : ParagraphAlignment.LEFT); XWPFRun run p.createRun(); run.setText(text); run.setFontSize(fontSize); run.setBold(bold); run.setFontFamily(宋体, XWPFRun.FontFamilyOption.HINTS); }注意setFontFamily(宋体, XWPFRun.FontFamilyOption.HINTS)这行中文字体能不能正确显示全看它。POI默认写入文档的字体可能不包含中文名导致生成的文件在自己的电脑上打开是乱码或者字体突然变回默认样式。实测下来用两个参数的setFontFamily方法显式指定中文用户的名字再配合setFontSize设置字号预览效果基本就和Word里手动排版一致了。2.3 表格与边框POI最容易踩的坑POI创建表格很简单doc.createTable(rows, cols)一行代码就能得到带指定行列数的XWPFTable。但问题来了直接创建的表格默认是没有边框的。你辛辛苦苦填了一堆数据打开文档发现表格线全都不见页面上一片空白非常崩溃。设置表格边框需要操作底层XML核心逻辑是拿到表格的CTTblPr然后往里面塞边框定义。这里有个依赖方面的注意点默认引入poi-ooxml时这些CT*类比如CTTblBorders、STBorder不一定存在于classpath中可能需要额外引入poi-ooxml-full。所以我在代码里把边框设置单独抽成了一个方法如果你只需要快速看效果可以先不调用它。2.4 图片插入与分页控制插入图片是导出Word时另一个高频需求。POI支持通过XWPFRun往段落里加图核心方法是addPicture()需要传四个参数图片输入流、图片类型、文件名、显示尺寸。run.addPicture(new FileInputStream(chart.png), Document.PICTURE_TYPE_PNG, chart.png, Units.toEMU(200), Units.toEMU(120));图片类型的常量有PICTURE_TYPE_PNG、PICTURE_TYPE_JPEG等实测对PNG和JPEG支持很好但GIF支持比较弱建议统一转成PNG或JPEG再插入。尺寸参数使用的是Units.toEMU()转换POI 4.1版本后才能这样写老版本需要自己算像素转换这是一个版本兼容上的小坑。分页控制也比较常用比如报告的最后要另起一页放附件明细。做法是在段落里加一个BreakType.PAGE的换行符XWPFRun run p.createRun(); run.addBreak(BreakType.PAGE);3. 可运行源码一个完整的Demo3.1 环境准备与依赖配置这套源码只需要JDK 8以上和Maven不需要额外的中间件。我用的是JDK 8 POI 5.2.3的组合生产环境实测稳定。Maven依赖如下dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version /dependencyJDK版本和POI版本要注意匹配。POI 5.x要求JDK 8及以上如果你用的是JDK 17完全没问题网上很多老教程还在用POI 3.x强行配JDK 11以上会出现各种NoClassDefFoundError所以别迷信老古董版本的依赖。3.2 完整源码一个可运行的导出工具类下面这套源码是我项目里抽出后精简过的版本可以直接复制运行。它演示了如何创建标题、正文段落、带边框的表格、插入图片、分页以及输出文件。import org.apache.poi.util.Units; import org.apache.poi.xwpf.usermodel.*; import java.io.FileInputStream; import java.io.FileOutputStream; public class WordExportDemo { public static void main(String[] args) throws Exception { XWPFDocument doc new XWPFDocument(); // 1. 标题 addParagraph(doc, 月度销售统计报告, 18, true, true); // 2. 报告时间 addParagraph(doc, 报告时间2025年2月, 12, false, false); // 3. 章节标题 addParagraph(doc, 一、总体综述, 14, true, false); // 4. 正文首行缩进用空格模拟简单直观 addParagraph(doc, 本月销售额较上月增长12%其中华东区贡献最大 新客户数量增长明显。整体业务呈稳步上升趋势。, 12, false, false); // 5. 数据表格 addSimpleTable(doc); // 6. 图表 XWPFParagraph imagePara doc.createParagraph(); imagePara.setAlignment(ParagraphAlignment.CENTER); XWPFRun imageRun imagePara.createRun(); imageRun.addPicture(new FileInputStream(chart.png), Document.PICTURE_TYPE_PNG, chart.png, Units.toEMU(400), Units.toEMU(240)); // 7. 分页后追加内容 addPageBreak(doc); addParagraph(doc, 以下为详细回款明细请参见附件。, 12, false, false); // 8. 输出 try (FileOutputStream fos new FileOutputStream(report.docx)) { doc.write(fos); } System.out.println(Word文档已生成report.docx); } private static void addParagraph(XWPFDocument doc, String text, int fontSize, boolean bold, boolean center) { XWPFParagraph p doc.createParagraph(); p.setAlignment(center ? ParagraphAlignment.CENTER : ParagraphAlignment.LEFT); XWPFRun run p.createRun(); run.setText(text); run.setFontSize(fontSize); run.setBold(bold); run.setFontFamily(宋体, XWPFRun.FontFamilyOption.HINTS); } private static void addPageBreak(XWPFDocument doc) { XWPFParagraph p doc.createParagraph(); p.createRun().addBreak(BreakType.PAGE); } private static void addSimpleTable(XWPFDocument doc) { String[][] rows { {区域, 销售额, 同比增长}, {华东, 120万, 18%}, {华北, 98万, 9%}, {华南, 75万, 6%} }; XWPFTable table doc.createTable(rows.length, 3); for (int i 0; i rows.length; i) { for (int j 0; j 3; j) { table.getRow(i).getCell(j).setText(rows[i][j]); } } } }这段代码的注意事项写在下面几条注释里addParagraph方法里的字体设置用的是两个参数的setFontFamily比单参数版本稳定得多。chart.png如果不存在程序会抛异常运行前记得放一张同名图片到项目根目录或者换成你自己的图片路径。addPageBreak是先新建一个段落再插入分页符比直接在原文末尾追加分页符更不容易干扰前面的排版。3.3 运行与验证用IDEA打开项目直接运行main方法控制台会打印“Word文档已生成report.docx”然后在项目根目录就能找到这个文件。双击打开你应该能看到标题居中、正文宋体、表格数据完整、图片正常显示。如果你写的是Web接口把最后的FileOutputStream换成ByteArrayOutputStream再把字节数组通过HTTP响应返回给前端就是一套完整的文件下载接口了。实测下来POI文档对象的写入可以反复调用write到不同输出流但注意同一个XWPFDocument不能多线程并发写需要每个线程持有自己的文档对象。4. 常见问题与实战排查4.1 文件打不开、提示损坏这是新手遇到最多的报错通常不是代码逻辑写错而是输出过程出了问题。我把实际排查顺序分享出来第一看输出文件的后缀是不是.docx。很多人为了兼容老系统把后缀改成.doc结果Word打开就提示“文件格式与扩展名不匹配”。POI的XWPF系列只认.docx.doc请死心。第二检查是不是多次调用doc.write()后没有关闭流。POI在高版本中强制要求使用后关闭底层流资源不关闭不仅可能锁文件甚至会写出不完整的zip结构导致Word打开报错。第三把生成的docx文件用解压工具打开看word/document.xml是否存在且结构完整。docx本身就是个zip包如果解压时提示压缩包损坏那基本可以断定是写入过程被中断了。4.2 中文和字号显示不对中文乱码或者字体丢失十有八九是字体设置问题。注意区别乱码是因为编码不对但POI内部统一用Unicode处理文本所以乱码反而不常见真正常见的坑是“字体没设对”也就是文档里看到的是默认字体或者是宋体但所有字的样式都一样。问题出在很多人只调了run.setFontFamily(宋体)但POI生成的XML里有多个字体属性分别对应ascii字体、eastAsia字体、hAnsi字体。只设置一个属性时中文字符可能命中的是eastAsia属性而你没有设置它。解决办法就是用两个参数的setFontFamily(宋体, FontFamilyOption.HINTS)或者直接在底层XML里同时设置。另一个经验是字体名称要用系统里实际存在的字体名比如Linux服务器上如果没装中文字体生成的文档打开后照样显示不了这是环境问题不是代码问题。此外还有个小细节run.setFontSize设置的是半角磅值默认是10.5磅五号。你想模拟小四12磅直接传12想模拟四号传14别记混。4.3 大文档内存溢出、导出慢如果你导出几千行甚至上万行的表格用POI直接生成Word可能会遇到OutOfMemoryError。原因很简单XWPFDocument会把整个文档的XML树常驻内存和Excel的SXSSF流式写入不同POI对Word没有成熟的流式写入方案。我踩过这个坑后总结出两条可行的路一是加大JVM堆内存比如-Xmx2g如果单文档不算太大这是最省事的方案。二是从业务上拆解限制单个Word文档的规模比如明细数据超过500行就分多个文件导出或者改成导出Excel。Word本身就不是为了承载超大表格设计的硬生成大文档既慢又容易崩不值得。如果文档结构固定、数据量又大可以换poi-tl模板方案它内部对内存的控制比原生POI好一些但也不是流式大数据量依然会吃内存。4.4 关于“可运行源码”的实用扩展建议源码能跑只是起点真正上生产前建议再补三块第一封装统一的导出工具类把addParagraph、addTable、addImage都做成公共方法参数化字体、对齐方式、缩进避免每个需求都复制一遍代码。我现在的工具类里方法不多但基本覆盖了日常90%的排版需求。第二处理异常时不要把异常吞掉。POI在文件写入时会抛各种IOException和IllegalStateException日志里要打出具体是哪个步骤失败方便排查。第三自动化测试。写一个测试用例运行完导出后再用POI重新解析生成的docx断言关键段落和表格数据是否命中。这一步能帮你拦截掉大部分格式回归问题。另外有个小技巧排查文档问题时如果手边没有Office可以用WPS打开或者直接解压docx查看XML重点看document.xml里的结构。我每次写完一段复杂的导出逻辑都会先解压看一眼XML基本能快速定位到问题到底出在段落上还是表格上。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门