准备工作:添加 POI 依赖
你需要在你的项目中添加 POI 的相关依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下内容:

<dependencies>
<!-- 核心 POI 库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 建议使用较新版本 -->
</dependency>
<!-- 处理 OOXML 格式 (如 .docx, .xlsx) 的核心库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 为了处理文档中的图片,需要这个依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
如果你使用 Gradle,则在 build.gradle 文件中添加:
implementation 'org.apache.poi:poi:5.2.5' implementation 'org.apache.poi:poi-ooxml:5.2.5' implementation 'org.apache.poi:ooxml-schemas:1.4'
核心概念
在开始编码前,了解几个核心类非常重要:
XWPFDocument: 代表一个.docx文档对象,它是所有操作的起点和容器,你可以用它来创建一个新文档,或者加载一个已有的.docx文件。XWPFParagraph: 代表文档中的一个段落,你可以向段落中添加文本、图片、超链接等。XWPFRun: 代表段落中具有相同格式的一段连续文本,一个段落可以包含多个XWPFRun,每个Run可以设置自己的字体、颜色、大小等样式。XWPFTable: 代表文档中的一个表格。XWPFTableRow: 代表表格中的一行。XWPFTableCell: 代表表格中的一个单元格。
基本流程:
- 创建
XWPFDocument对象。 - 通过
document.createParagraph()创建段落。 - 通过
paragraph.createRun()创建文本运行块。 - 通过
run.setText()设置文本内容,并可以设置样式。 - (可选)创建表格、插入图片等。
- 通过
document.write()将文档写入输出流(如FileOutputStream)。 - 关闭所有资源。
基础操作示例
下面是一个完整的 Java 示例,展示了如何创建一个 Word 文档,并添加带样式的文本、标题和列表。

import org.apache.poi.xwpf.usermodel.*;
import java.io.FileOutputStream;
import java.io.IOException;
public class SimpleWordGenerator {
public static void main(String[] args) {
// 1. 创建一个新的 XWPFDocument 对象
try (XWPFDocument document = new XWPFDocument()) {
// 2. 创建一个段落并添加标题
XWPFParagraph titleParagraph = document.createParagraph();
titleParagraph.setAlignment(ParagraphAlignment.CENTER); // 设置居中对齐
XWPFRun titleRun = titleParagraph.createRun();
titleRun.setText("POI 生成 Word 文档示例");
titleRun.setBold(true); // 设置为粗体
titleRun.setFontFamily("微软雅黑"); // 设置字体
titleRun.setFontSize(18); // 设置字号
// 3. 创建一个普通段落
XWPFParagraph paragraph1 = document.createParagraph();
XWPFRun run1 = paragraph1.createRun();
run1.setText("这是一个使用 Apache POI 生成的 Word 文档段落。");
run1.setFontSize(12);
run1.setItalic(true); // 设置为斜体
// 4. 创建另一个段落,并添加列表
XWPFParagraph listParagraph = document.createParagraph();
listParagraph.setNumID(getNumId(document)); // 应用列表样式
XWPFRun listRun1 = listParagraph.createRun();
listRun1.setText("列表项一");
XWPFParagraph listParagraph2 = document.createParagraph();
listParagraph2.setNumID(getNumId(document)); // 应用相同的列表样式
XWPFRun listRun2 = listParagraph2.createRun();
listRun2.setText("列表项二");
// 5. 创建一个二级标题
XWPFParagraph heading2 = document.createParagraph();
heading2.setSpacingAfter(200); // 段后间距
XWPFRun heading2Run = heading2.createRun();
heading2Run.setText("二级标题");
heading2Run.setBold(true);
heading2Run.setFontSize(14);
heading2Run.setUnderline(UnderlinePatterns.SINGLE); // 添加下划线
// 6. 将文档写入到本地文件
try (FileOutputStream out = new FileOutputStream("D:/poi-example.docx")) {
document.write(out);
System.out.println("Word 文档生成成功!文件路径: D:/poi-example.docx");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 为段落应用列表样式,这是一个简单的实现,创建一个默认的编号列表。
* @param document XWPFDocument 对象
* @return 列表的编号 ID
*/
private static BigInteger getNumId(XWPFDocument document) {
// 创建一个默认的编号列表定义
CTNumbering numbering = CTNumbering.Factory.newInstance();
CTAbstractNum abstractNum = numbering.addNewAbstractNum();
abstractNum.setAbstractNumId(BigInteger.valueOf(1));
CTLevel level = abstractNum.addNewLvl();
level.setIlvl(BigInteger.ZERO); // 第一级列表
level.addNewStart().setVal(BigInteger.ONE); // 从 1 开始
level.addNumFmt().setVal("decimal"); // 使用数字格式 (1, 2, 3...)
// 将编号定义添加到文档中
CTNumbering numberingInstance = document.getCTNumbering();
if (numberingInstance == null) {
document.setNumbering(numbering);
} else {
numberingInstance.setAbstractNum(abstractNum);
}
return BigInteger.valueOf(1); // 返回这个列表的 ID
}
}
进阶操作
1 插入表格
创建和填充表格非常直观。
// ... 在创建文档之后,写入文件之前 ...
// 创建一个 3行4列 的表格
XWPFTable table = document.createTable(3, 4);
// 获取第一行
XWPFTableRow tableRowOne = table.getRow(0);
// 设置第一行的单元格文本
tableRowOne.getCell(0).setText("姓名");
tableRowOne.getCell(1).setText("年龄");
tableRowOne.getCell(2).setText("性别");
tableRowOne.getCell(3).setText("城市");
// 获取第二行
XWPFTableRow tableRowTwo = table.getRow(1);
tableRowTwo.getCell(0).setText("张三");
tableRowTwo.getCell(1).setText("30");
tableRowTwo.getCell(2).setText("男");
tableRowTwo.getCell(3).setText("北京");
// 获取第三行
XWPFTableRow tableRowThree = table.getRow(2);
tableRowThree.getCell(0).setText("李四");
tableRowThree.getCell(1).setText("25");
tableRowThree.getCell(2).setText("女");
tableRowThree.getCell(3).setText("上海");
// 可以合并单元格,例如合并第一行的后三个单元格
tableRowOne.getCell(1).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
tableRowOne.getCell(2).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
tableRowOne.getCell(3).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
2 插入图片
插入图片需要指定图片的宽度和高度(单位是 EMU - English Metric Units, 1 英寸 = 914400 EMU)。
// ... 在创建文档之后,写入文件之前 ...
// 创建一个段落来放置图片
XWPFParagraph pictureParagraph = document.createParagraph();
XWPFRun pictureRun = pictureParagraph.createRun();
// 图片路径
String imagePath = "D:/path/to/your/image.png"; // 请替换为你的图片路径
try (java.io.InputStream picIn = new java.io.FileInputStream(imagePath)) {
// 图片宽度 (400 EMU)
int width = 400;
// 图片高度 (300 EMU)
int height = 300;
pictureRun.addPicture(picIn, XWPFDocument.PICTURE_TYPE_PNG, "image.png", Units.toEMU(width), Units.toEMU(height));
pictureRun.addBreak(); // 换行
} catch (Exception e) {
e.printStackTrace();
}
完整综合示例
下面是一个结合了上述所有功能的完整示例。
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
public class ComprehensiveWordExample {
public static void main(String[] args) {
String outputFilePath = "D:/comprehensive-example.docx";
String imagePath = "D:/path/to/your/logo.png"; // 替换为你的图片路径
try (XWPFDocument document = new XWPFDocument()) {
// --- 1. 标题和Logo ---
XWPFParagraph titleP = document.createParagraph();
titleP.setAlignment(ParagraphAlignment.CENTER);
// 添加Logo
try (FileInputStream picIn = new FileInputStream(imagePath)) {
titleP.createRun().addPicture(picIn, XWPFDocument.PICTURE_TYPE_PNG, "logo.png", Units.toEMU(100), Units.toEMU(50));
}
XWPFRun titleR = titleP.createRun();
titleR.addBreak(); // 换行
titleR.setText("年度报告");
titleR.setBold(true);
titleR.setFontSize(24);
titleR.setFontFamily("宋体");
titleR.addBreak(); // 换行
titleR.addBreak(); // 换行
// --- 2. ---
XWPFParagraph summaryP = document.createParagraph();
summaryP.setSpacingAfter(200);
XWPFRun summaryR = summaryP.createRun();
summaryR.setText("本报告总结了2025年度的主要工作成果和财务状况。");
summaryR.setFontSize(12);
summaryR.setItalic(true);
// --- 3. 列表 ---
BigInteger numId = createNumberedList(document);
addListItem(document, "完成了所有预定项目目标。", numId);
addListItem(document,"实现了销售额同比增长20%。", numId);
addListItem(document,"成功拓展了两个新的市场区域。", numId);
// --- 4. 表格 ---
createAndFillTable(document);
// --- 5. 写入文件 ---
try (FileOutputStream out = new FileOutputStream(outputFilePath)) {
document.write(out);
System.out.println("综合 Word 文档生成成功!文件路径: " + outputFilePath);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void addListItem(XWPFDocument doc, String text, BigInteger numId) {
XWPFParagraph p = doc.createParagraph();
p.setNumID(numId);
XWPFRun r = p.createRun();
r.setText(text);
}
private static BigInteger createNumberedList(XWPFDocument doc) {
CTNumbering numbering = CTNumbering.Factory.newInstance();
CTAbstractNum abstractNum = numbering.addNewAbstractNum();
abstractNum.setAbstractNumId(BigInteger.ONE);
CTLevel level = abstractNum.addNewLvl();
level.setIlvl(BigInteger.ZERO);
level.addNewStart().setVal(BigInteger.ONE);
level.addNumFmt().setVal("decimal");
doc.getCTDocument().getBody().setNumbering(numbering);
return BigInteger.ONE;
}
private static void createAndFillTable(XWPFDocument doc) {
XWPFTable table = doc.createTable(4, 2);
table.setTableAlignment(TableAlignment.CENTER);
// 表头样式
XWPFTableRow headerRow = table.getRow(0);
headerRow.getCell(0).setText("指标");
headerRow.getCell(1).setText("数值");
applyHeaderStyle(headerRow);
// 表格数据
table.getRow(1).getCell(0).setText("总营收");
table.getRow(1).getCell(1).setText("¥5,000,000");
table.getRow(2).getCell(0).setText("净利润");
table.getRow(2).getCell(1).setText("¥1,200,000");
table.getRow(3).getCell(0).setText("员工人数");
table.getRow(3).getCell(1).setText("150");
}
private static void applyHeaderStyle(XWPFTableRow row) {
for (XWPFTableCell cell : row.getTableCells()) {
cell.setColor("A9A9A9"); // 灰色背景
CTTblPr tablePr = row.getTable().getCTTbl().getTblPr();
if (tablePr == null) {
row.getTable().getCTTbl().setTblPr(CTTblPr.Factory.newInstance());
}
// 可以在这里添加更复杂的样式,如边框
cell.getCTTc().getTcPr().addNewShd().setFill("D3D3D3"); // 设置单元格背景色
for (XWPFParagraph p : cell.getParagraphs()) {
for (XWPFRun r : p.getRuns()) {
r.setBold(true);
r.setColor("FFFFFF"); // 白色文字
r.setFontFamily("微软雅黑");
}
}
}
}
}
总结与注意事项
.docxvs.doc: POI 主要支持.docx格式,对于旧版的.doc格式,需要使用 HWPF 库,但功能较弱且已不推荐。- 样式复杂性: POI 对 Word 样式的支持是有限的,特别是复杂的样式(如自定义标题样式、页眉页脚的复杂布局)可能需要直接操作底层的 XML,或者考虑使用更专业的模板引擎。
- 模板引擎: 对于复杂的报告生成,通常推荐使用 模板引擎(如 FreeMarker, Thymeleaf)结合一个预先设计好的 Word 模板(
.docx),你只需要在模板中用${variable}这样的占位符标记需要替换的内容,然后引擎会根据数据生成最终的 Word 文件,这种方法更灵活、更易于维护,对于.docx模板,可以使用 Apache POI 的XWPF来读取和修改模板中的占位符。
