最近有球友问:三哥,解析PDF用哪个工具比较好?
市面上主流的Java PDF处理库,大概分成三类:iText(商业授权,AGPL对商业不友好)、OpenPDF(iText 4的分支,LGPL/MPL协议)、以及今天要聊的Apache PDFBox(Apache 2.0协议,对商业最友好) 。
PDFBox是目前唯一一个由Apache基金会维护的Java PDF处理库,功能覆盖从文本提取到数字签名的全场景。
截至2026年7月,PDFBox的最新版本已经更新到3.0.8。
今天这篇文章,我就把PDFBox从安装到实战、从底层原理到性能调优,从头到尾给你拆解一遍。
希望对你会有所帮助。
Apache PDFBox是一个开源的Java库,专门用于处理PDF文档。
它允许你:
简单说,只要是跟PDF相关的事,PDFBox基本都能干。
在Java生态里,PDF处理库其实不少。但PDFBox有几个独特的优势:
第一,Apache 2.0协议,对商业最友好。 iText从7.0开始采用AGPL协议,如果你的项目是商业闭源软件,要么花钱买授权,要么开源全部代码。而PDFBox采用Apache 2.0协议,允许自由使用、修改和分发,甚至用于商业闭源产品。
第二,Apache顶级项目,社区活跃。 PDFBox是Apache软件基金会的顶级项目,有持续的版本迭代和社区支持。2026年3月至今,PDFBox已经连续发布了3.0.6、3.0.7和3.0.8等多个版本。
第三,“一站式”解决方案。 从最简单的文本提取到复杂的数字签名,从基本的文档合并到高级操作,PDFBox几乎涵盖了PDF处理的所有场景。
PDFBox 3.x需要Java 8+环境。
如果你的项目用的是Maven:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.8</version>
</dependency>
如果用的是Gradle:
implementation 'org.apache.pdfbox:pdfbox:3.0.8'
版本选择建议:PDFBox 3.x是目前最常用的Java PDF处理起点。
截至2026年7月,最新版本是3.0.8。
如果你的项目还在用2.x,建议升级到3.x——性能更好、API更规范。
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.File;
import java.io.IOException;
public class ExtractText {
public static void main(String[] args) throws IOException {
File file = new File("sample.pdf");
// PDFBox 3.x 使用 Loader.loadPDF(),不再是 PDDocument.load()
try (PDDocument document = Loader.loadPDF(file)) {
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
System.out.println(text);
}
}
}
关键注意:PDFBox 3.0移除了PDDocument.load()方法,必须使用Loader.loadPDF()。
提取指定页面范围的文本:
try (PDDocument document = Loader.loadPDF(new File("sample.pdf"))) {
PDFTextStripper stripper = new PDFTextStripper();
stripper.setStartPage(2); // 从第2页开始
stripper.setEndPage(4); // 到第4页结束
String text = stripper.getText(document);
System.out.println(text);
}
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
public class CreatePDF {
public static void main(String[] args) {
try (PDDocument document = new PDDocument()) {
// 添加一个空白页
PDPage page = new PDPage();
document.addPage(page);
// 创建内容流
PDPageContentStream contentStream = new PDPageContentStream(document, page);
// 设置字体和大小
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
contentStream.beginText();
contentStream.newLineAtOffset(100, 700); // 坐标:距离左侧100,距离底部700
contentStream.showText("Hello, PDFBox!");
contentStream.endText();
contentStream.close();
document.save("HelloPDFBox.pdf");
} catch (Exception e) {
e.printStackTrace();
}
}
}
坐标系统说明:PDF的坐标原点在页面左下角,X轴向右为正,Y轴向上为正。这与Swing等GUI库的坐标系统(左上角为原点)正好相反,新手容易踩坑。
处理多行文本:
contentStream.setLeading(14.5f); // 设置行间距
contentStream.showText("第一行文本");
contentStream.newLine();
contentStream.showText("第二行文本");
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
public class ExtractImages {
public static void main(String[] args) throws IOException {
try (PDDocument document = Loader.loadPDF(new File("sample.pdf"))) {
for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) {
PDPage page = document.getPage(pageIndex);
PDResources resources = page.getResources();
// 遍历页面中的所有资源
for (COSName name : resources.getXObjectNames()) {
if (resources.isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) resources.getXObject(name);
// image.getImage() 可以获取 BufferedImage 对象
System.out.println("找到图片:" + name.getName());
}
}
}
}
}
}
PDFBox提供了PDFMergerUtility工具类来合并多个PDF:
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
public class MergePDFs {
public static void main(String[] args) throws IOException {
PDFMergerUtility merger = new PDFMergerUtility();
// 添加源文件
merger.addSource(new File("file1.pdf"));
merger.addSource(new File("file2.pdf"));
merger.addSource(new File("file3.pdf"));
// 设置目标文件
merger.setDestinationFileName("merged.pdf");
// 执行合并
merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
}
}
PDF加密需要两个密码:用户密码(打开查看,权限受限)和所有者密码(完全访问权限):
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
public class EncryptPDF {
public static void main(String[] args) throws IOException {
try (PDDocument document = Loader.loadPDF(new File("sample.pdf"))) {
AccessPermission ap = new AccessPermission();
// 允许打印,但不允许修改
ap.setCanPrint(true);
ap.setCanModify(false);
StandardProtectionPolicy spp = new StandardProtectionPolicy(
"user_password", // 用户密码
"owner_password", // 所有者密码
ap
);
spp.setEncryptionKeyLength(128);
document.protect(spp);
document.save("encrypted.pdf");
}
}
}
PDFBox支持操作AcroForm表单字段:
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;
public class FillForm {
public static void main(String[] args) throws IOException {
try (PDDocument document = Loader.loadPDF(new File("form.pdf"))) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
// 获取表单字段并填充
PDTextField nameField = (PDTextField) acroForm.getField("name");
if (nameField != null) {
nameField.setValue("张三");
}
PDTextField emailField = (PDTextField) acroForm.getField("email");
if (emailField != null) {
emailField.setValue("zhangsan@example.com");
}
document.save("filled_form.pdf");
}
}
}
有些小伙伴可能会好奇:“PDFBox到底是怎么把一团乱码的PDF文件变成可读的文本和图片的?”
理解底层原理,能帮你更好地理解PDFBox的能力边界和性能瓶颈。
PDF文件本质上是一个字节序列。
它的物理结构分为四个部分:

%PDF-1.7)PDFBox采用双层架构来分离底层PDF结构和高层业务语义:

COS层(底层对象层) :直接映射PDF文件中的基本对象——字典(COSDictionary)、数组(COSArray)、流(COSStream)等。你可以通过COS层访问PDF文件的任何底层细节。
PD层(高层语义层) :在COS层之上封装了面向开发者的友好API——PDDocument代表整个文档,PDPage代表一页,PDPageContentStream代表内容流。
这种分层设计的核心价值在于:你可以通过PD层快速完成常规操作,遇到复杂需求时又能随时下沉到COS层获取完全控制权。
PDFBox的内容处理架构分为三个层次:
第一层:基础处理层(PDFStreamEngine)
这是最底层。它负责解析内容流为Token序列,将操作分发给注册的处理器,维护图形状态栈,管理资源。
简单说,它把PDF的“二进制指令流”翻译成“可执行的操作序列”。
第二层:图形抽象层(PDFGraphicsStreamEngine)
这层定义了图形操作的抽象方法——路径构建、填充、描边、绘制图像。它不关心“怎么画”,只定义“画什么”。
第三层:实现层(PageDrawer、PDFTextStripper等)
这层提供了具体的实现。PageDrawer负责渲染到Graphics2D,PDFTextStripper负责提取文本。
以提取文本为例,PDFTextStripper就是通过重写showGlyph()方法来收集字符的位置信息,从而实现文本提取。

这种分层设计让PDFBox具有极高的可扩展性——你可以继承PDFStreamEngine来实现自定义的行为,比如内容分析或自定义渲染后端。
如果你从PDFBox 2.x升级到3.x,这几个变化必须注意:
变化一:PDDocument.load()被移除
// PDFBox 2.x
PDDocument document = PDDocument.load(new File("sample.pdf"));
// PDFBox 3.x
PDDocument document = Loader.loadPDF(new File("sample.pdf"));
变化二:IO模块重构
PDFBox 3.0引入了全新的IO模块和Loader类,这是3.0版本中变化最大的部分。
PDFBox默认的PDType1Font不支持中文。要用中文,你需要自己加载中文字体:
// 加载中文字体
PDType0Font font = PDType0Font.load(document, new File("simsun.ttf"));
contentStream.setFont(font, 12);
PDFBox在处理大型或复杂的PDF文件时,性能可能比其他PDF库慢一些。优化建议:
Loader.loadPDF()时指定MemoryUsageSetting来控制内存使用try-with-resources确保资源及时释放1. Apache 2.0协议,商业友好可以自由使用、修改和分发,甚至用于商业闭源产品。
2. 功能全面,“一站式”解决方案从文本提取到数字签名,从文档合并到OCR集成,覆盖几乎所有PDF处理场景。
3. Apache顶级项目,社区活跃持续的版本迭代,2026年已连续发布多个版本。
4. API设计直观即使没有PDF处理经验的开发者也能快速上手。
5. 命令行工具PDFBox还包含一套命令行工具,可以执行常见的PDF处理任务。
6. 纯Java实现可以在任何支持Java的平台上运行,无需依赖特定操作系统。
1. 大文件性能偏弱处理非常大的或结构复杂的PDF文件时,速度可能不如商业库。
2. 表格提取不是原生功能PDFBox提取表格需要结合Tabula-java等工具。
3. 中文支持需要额外配置默认字体不支持中文,需要手动加载中文字体。
4. 3.0版本有破坏性变更从2.x升级到3.x需要注意API变化。
场景 | 推荐程度 | 理由 |
|---|---|---|
文档管理系统 | ✅✅✅ 强烈推荐 | 创建、编辑、提取PDF内容 |
报表自动生成 | ✅✅✅ 强烈推荐 | 动态生成包含图表和表格的复杂PDF报表 |
合同/发票解析 | ✅✅✅ 强烈推荐 | 提取结构化数据,Apache 2.0协议商业友好 |
电子书阅读器 | ✅✅ 推荐 | 支持文本搜索和内容提取 |
数字签名 | ✅✅ 推荐 | PDFBox提供符合PDF安全规范的数字签名 |
PDF加密/解密 | ✅✅ 推荐 | 支持用户密码和所有者密码双重加密 |
PDF合并/拆分 | ✅✅ 推荐 | PDFMergerUtility工具类 |
Apache PDFBox作为一个Apache基金会的顶级项目,用Apache 2.0协议给了商业开发者最大的自由度——你可以免费使用、自由修改,甚至用于商业闭源产品。
它的功能覆盖从创建、编辑、提取到加密、签名、合并的几乎所有PDF操作。而且它的API设计直观,即使没有PDF处理经验的开发者也能快速上手。
当然,PDFBox也不是银弹。
处理超大文件时性能可能不如商业库,表格提取需要借助额外工具。
但对于绝大多数PDF处理需求,PDFBox已经足够强大。
我的建议是:如果你的项目需要PDF处理能力,先花一个下午把PDFBox跑一遍
。感受一下“免费、开源、功能全面”的PDF处理体验——你会发现,很多事情根本不需要花钱。
开源地址