PDF作成や書き込みで遭遇する「失敗」―それは多くの開発者にとって頭痛の種です。
PDFBox は Apache が提供している Java 用の PDF ライブラリで、機能は豊富ですが、使いこなす上で「保存が失敗する」原因は様々です。
このガイドでは、PDFBox で PDF を保存する際に起こり得るエラーの発生メカニズムをわかりやすく解説し、実際のコードとともに失敗を防ぐためのベストプラクティスを網羅します。
手順に従うだけで、ほぼすべてのケースをカバーし、トラブルシューティングをスムーズに行えるように設計しています。
なぜ PDFBox を使うのか?
- オープンソース – 無償で利用可能。商用プロジェクトでも安心。
- Java ネイティブ – JVM 上でそのまま動作。ビルドツール(Maven/Gradle)との統合が簡単。
- 機能充実 – PDF の読み込み・編集・生成・結合・分割、テキスト抽出、署名など。
- コミュニティ – バグ報告と機能拡張に活発。質問が少なく、サポートが期待できる。
1. 開発環境設定
1‑1. JDK のインストール
PDFBox は JDK 11 以降で開発・テストされています。
お使いの環境に問題が無ければ、JDK 17 LTS(長期サポート)をおすすめします。
# 例: SDKMAN! で JDK 17 をインストール
sdk install java 17.0.8-tem
sdk use java 17.0.8-tem
1‑2. ビルドツール
Maven
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.26</version>
</dependency>
<!-- PDFBox 2.0.26 (2023年11月) を使用 -->
</dependencies>
Gradle (Kotlin DSL)
// build.gradle.kts
dependencies {
implementation("org.apache.pdfbox:pdfbox:2.0.26")
}
2. PDFBox で PDF を「保存」する基本フロー
以下のサンプルは、PDF を新規作成し、単純なテキストを挿入して指定したパスに保存する手順です。
大きないわゆる「書き込み失敗」の要因を最小化するため、ファイルを確実に開放する点を強調しています。
import java.io.File;
import java.io.IOException;
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 PdfCreator {
public static void main(String[] args) {
String outputPath = "output/sample.pdf";
createPdf(outputPath);
}
public static void createPdf(String path) {
// 1. 資料の準備
PDDocument document = new PDDocument();
try {
// 2. ページ追加
PDPage page = new PDPage();
document.addPage(page);
// 3. 内容書き込み
try (PDPageContentStream contentStream =
new PDPageContentStream(document, page)) {
contentStream.beginText();
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
contentStream.newLineAtOffset(100, 700);
contentStream.showText("Hello, PDFBox!");
contentStream.endText();
} // <- contentStream がここで自動クローズ
// 4. 文書保存
document.save(new File(path));
} catch (IOException e) {
e.printStackTrace(); // エラーは必ずログに出力
} finally {
// 5. 文書を閉じる(必須)
try {
document.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
ポイント
| # | チェックリスト | 目的 |
|---|---|---|
| 1 | try-with-resources を利用 | PDPageContentStream は必ずクローズ。リソースリークを防止 |
| 2 | finally ブロック で document.close() |
文書内部の全リソース解放。ファイルロックを離す |
| 3 | document.save() → File オブジェクト |
省略可能なパス文字列でも可だが、File を明示するとエラーを把握しやすい |
| 4 | IOException ハンドリング | エラー発生時に詳細をログに残す |
3. 「保存」に失敗しやすい代表的なシナリオ
Q1. 「Permission denied」と出る
A1. ファイルやディレクトリに書き込み権限が無い場合に発生します。
対策:
chmod 777で権限を付与(開発環境のみ)- 実行ユーザーが対象ディレクトリへ書き込み権限を持つことを確認
- Windows なら「管理者として実行」
Q2. 「FileAlreadyExistsException」
A2. 同名ファイルが既にあるので、上書き拒否設定が原因です。
対策:
document.save(new File(path), true)で上書きを許可- ファイルがロックされている場合には、後処理で閉じてから再試行
Q3. 「OutOfMemoryError」
A3. 大きい PDF を生成する際に JVM のヒープが不足します。
対策:
-Xmxでヒープサイズを増やすPDDocumentのsetAllSecurityPolicies()で暗号化を解除し、メモリ使用を抑える- 複数段階で PDF を分割して書き込む
Q4. 「NullPointerException: contentStream is null」
A4.PDPageContentStreamを生成できなかったとき、document.close()前でcontentStreamを参照したことが原因。
対策:
tryブロックをcontentStream処理の外側に設けているか確認contentStreamの生成失敗時にはdocument.close()を必ず呼ぶ
Q5. ファイルが破損している
A5. 途中で書き込みが中断されたり、ディスク障害がある場合。
対策:
- 事前にディスクの空き容量を確認
- 書き込み前に一時ファイルへ保存し、処理成功後に名前変更
4. 典型的なトラブルシューティング手順
-
ログの確認
- すべての例外をログに残す (
e.printStackTrace();だけでなく、SLF4J を利用して永続ログを残す)
- すべての例外をログに残す (
-
リソースのクリーンアップ
try-with-resourcesを徹底finallyでdocument.close()を確実に実行
-
パスと権限の二重チェック
new File(path).getCanonicalPath()で実際に書き込もうとしているディレクトリを確認- 必要なら
Files.createDirectories(Paths.get(dir))でディレクトリ生成
-
再実行を自動化する
- 同じファイルへの複数書き込みは失敗しやすい。
- 書き込み失敗時に 1 秒待ってから再試行(最大3回まで)
-
メモリ使用量を可視化
Runtime.getRuntime().totalMemory()で現在のヒープ使用量を取得- 生成途中で
System.gc()を強制呼び出し(ただし頻繁な使用は望ましくない)
-
バージョン確認
- PDFBox の新リリースにはバグフィックスが追加されているので、常に最新(安定版)を使用
5. 実用的な「上書き OK」コード
import java.io.File;
import java.io.IOException;
public class OverwriteExample {
public static void main(String[] args) {
String path = "output/overwriteSample.pdf";
overwritePdf(path);
}
public static void overwritePdf(String path) {
File target = new File(path);
// 既存ファイルの存在チェック
if (target.exists()) {
// 規定の上書きルールを設定(force = true)
try (PDDocument doc = PDDocument.load(target)) {
// 必要なら内容を編集
doc.setVersion(1.7f);
} catch (IOException e) {
e.printStackTrace();
}
}
// ここで新規作成・保存
try (PDDocument doc = new PDDocument()) {
doc.addPage(new PDPage());
doc.save(target); // 規定で上書き
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 重要:
document.save(target)は true を渡すことで、上書き時にOverwriteActionで上書きを許可する。 - 既存ファイルを読み込む際に
PDDocument.load()で内容を取得し、必要なら編集してから保存するとエラー率が低くなる。
6. 大きな PDF の効率的な書き込み
| 手法 | 説明 | 例 |
|---|---|---|
| Incremental Update | 既存の PDF に対して差分だけを書き込む(サイズ削減) | document.saveIncremental(new File(target)); |
PDFBox 2.0 の OutputStream バイパス |
PDDocument で直接 OutputStream を渡すことでフレームワークのバッファリングを制御 |
document.save(new BufferedOutputStream(...)); |
| ガベージコレクションのスケジューリング | 大量オブジェクト生成後に System.gc() 呼び出しでメモリをフラッシュ |
document.close(); System.gc(); |
7. デバッグ時に便利なツールと設定
| ツール/設定 | 用途 |
|---|---|
PDFBox org.apache.pdfbox.logging |
ログレベルを DEBUG にして内部処理を可視化 |
-Dorg.apache.pdfbox.debug=true |
PDFBox 内部デバッグ情報を標準出力に表示 |
JVM の -Xdiag |
低レベルのヒープ診断 |
| IDE のブレークポイント | PDDocument#save 前に状態を確認 |
8. まとめ — 「保存失敗」を未然に防ぐチェックリスト
- パス & 権限 → 書き込み権限を確認して一時ファイルに書き込み、成功後に名前変更
- リソース管理 →
try-with-resources、finallyで必ずリソースを解放 - バージョン管理 → PDFBox 2.x の最新安定版を使用
- メモリ管理 → 大きな PDF は
-Xmxを調整し、必要なら分割書き込み - 上書き対策 →
document.save(file, true)で上書きを明示 - エラーハンドリング → すべての IOException をロギングし、再試行ロジックを実装
これらを実践することで、PDFBox を使った PDF の保存時に起こりやすい失敗を ほぼゼロ に近づけることができます。
「保存が失敗する」問題に直面した際は、上記チェックリストを参照し、原因を段階的に絞り込んでください。
成功した PDF を手にした瞬間、安心感とともに「もうトラブルは来ないだろう」という信頼が生まれます。


コメント