Introduction

法務部門が複数の契約書PDFを受け取ると、各レビュアーが機密条項を保護するためにそれぞれパスワードを設定します。パスワードが異なるため、ファイルを直接結合できず、単一のバインダーにまとめるのは悪夢のようです。手動での復号は時間がかかり、エラーが発生しやすく、特に数十件のPDFを扱う場合は大変です。

Password‑merge は Java 用の GroupDocs.Merger ワークフローで、異種の PDF を解除し、統一された認証情報で再保護した単一の PDF を作成します。このチュートリアルでは、保護された PDF の検出、解除、結合、そしてオプションで統一パスワードのローテーションまでの手順を解説します。

30 行未満の Java コードで、Merger API の設定、バイトストリームの処理、セキュアな結合 PDF の生成方法を学びます。

When should I merge password‑protected PDFs?

パスワードで保護された PDF を結合するのは、複数のパスワードを管理する手間を省きつつ、セキュリティポリシーを遵守した検索可能な単一アーカイブが必要なときに有効です。典型的なシナリオは、四半期ごとの財務報告書バンドル、監査用の契約バインダー、各貢献者が異なるパスワードを設定した法的案件ファイルなどです。各ファイルをプログラムで解除し、統一パスワードを適用することで、アーカイブの安全性を保ちつつ、下流のレビュー工程を簡素化できます。この一連の操作は CI パイプラインで自動化でき、手作業に費やす時間を大幅に削減します。

Prerequisites

  • Java 11 以上
  • GroupDocs.Merger for Java 24.6+(temporary license
  • 任意でパスワードが設定された PDF ファイルのセット

Maven でライブラリをインストールします:

mvn dependency:copy -Dartifact=com.groupdocs:groupdocs-merger:24.6

Step 1 – Detect if a PDF is password‑protected

ファイルを解除しようとする前に、実際にパスワードが設定されているか確認します。これにより不要な処理を防ぎ、どのファイルが認証情報を必要としているかをログに記録できます。

// Returns true if the PDF at `path` has an owner or user password
public boolean isDocumentProtected(String path, String password) {
    Merger merger;
    if (password == null || password.isEmpty()) {
        merger = new Merger(path);
    } else {
        merger = new Merger(path, new LoadOptions(password));
    }
    try {
        return merger.isPasswordSet();
    } finally {
        merger.dispose();
    }
}

Key points:

  • LoadOptions で既知のパスワードを渡します。パスワードが無い場合は通常通りファイルを開きます。
  • isPasswordSet() は所有者パスワードとユーザーパスワードの両方に対して true を返します。
  • ネイティブリソースを解放するため、Merger インスタンスは必ず dispose() してください。

Step 2 – Unlock each PDF and collect raw bytes

キーがファイルパス、値がパスワード(無い場合は null)となっているマップを走査します。メソッドは解除された PDF を表すバイト配列のリストを返します。

public List<byte[]> unlockAll(Map<String, String> sources) throws IOException {
    List<byte[]> unlocked = new ArrayList<>();
    for (Map.Entry<String, String> e : sources.entrySet()) {
        String path = e.getKey();
        String password = e.getValue();
        System.out.println("Unlocking (credentials=" +
                (password != null ? "yes" : "no") + "): " + path);
        if (password == null || password.isEmpty()) {
            unlocked.add(Files.readAllBytes(Paths.get(path)));
        } else {
            LoadOptions opts = new LoadOptions(password);
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            Merger m = new Merger(path, opts);
            try {
                m.removePassword();
                m.save(buf);
            } finally {
                m.dispose();
            }
            unlocked.add(buf.toByteArray());
        }
    }
    return unlocked;
}

Key points:

  • removePassword() が既存の保護を除去します。
  • 復号された内容は ByteArrayOutputStream に書き込まれ、メモリ上で扱います。
  • パスワードが設定されていないファイルは直接読み込んでフローをシンプルに保ちます。

Step 3 – Merge the unlocked PDFs and apply a unified password

最初の PDF ストリームから Merger を作成し、残りのストリームを結合します。最後に AddPasswordOptions で統一パスワードを設定します。

public void mergeAndProtect(List<byte[]> unlockedPdfs,
                            String unifiedPassword,
                            String outputPath) {
    InputStream first = new ByteArrayInputStream(unlockedPdfs.get(0));
    Merger merger = new Merger(first);
    try {
        for (int i = 1; i < unlockedPdfs.size(); i++) {
            merger.join(new ByteArrayInputStream(unlockedPdfs.get(i)));
        }
        merger.addPassword(new AddPasswordOptions(unifiedPassword));
        merger.save(outputPath);
    } finally {
        merger.dispose();
    }
    System.out.println("Merged output: " + new File(outputPath).getAbsolutePath());
    System.out.println("Unified password: " + unifiedPassword);
}

Key points:

  • addPassword で最終的な PDF を指定した認証情報で暗号化します。
  • すべての処理はメモリ上で行われ、最終ファイルだけがディスクに書き出されます。
  • ネイティブハンドルを解放するため、Merger は必ず dispose() してください。

Step 4 – Rotate the password on an already‑protected PDF (optional)

組織で定期的なパスワードローテーションが求められる場合、元のファイルを再結合せずに認証情報だけを更新できます。

public void rotateUnifiedPassword(String path,
                                 String oldPassword,
                                 String newPassword,
                                 String outputPath) {
    Merger merger = new Merger(path, new LoadOptions(oldPassword));
    try {
        merger.updatePassword(new UpdatePasswordOptions(newPassword));
        merger.save(outputPath);
    } finally {
        merger.dispose();
    }
    System.out.println("Rotated output: " + new File(outputPath).getAbsolutePath());
    System.out.println("New password: " + newPassword);
}

Key points:

  • 現在のパスワードで保護された PDF をロードします。
  • updatePassword で新しい認証情報に置き換えます。
  • コンテンツを再処理しないため、処理は高速です。

Real‑World Application

私は、5 件の投資家契約書を統合する際に、各契約書がレビュアーごとに異なるパスワードで保護されているという問題に直面しました。上記の手順を使ってすべてのファイルを解除し、単一のバインダーに結合、さらに社内ポリシーに合わせた統一パスワードを設定しました。標準的なノートPC でも 2 分未満で完了しました。

Best Practices

  • パスワードを早期に検証: isDocumentProtected を使って手動対応が必要なファイルを事前にフラグ付けします。
  • メモリ使用量を抑制: 大容量 PDF の場合は、すべてのバイト配列をメモリに保持せず、ディスクにストリームするようにします。
  • オブジェクトは速やかに破棄: Merger クラスはネイティブリソースを保持するため、finally ブロックで必ず dispose() を呼び出します。
  • 開発時は一時ライセンスのみ使用: 本番環境では必ず正式ライセンスを取得してください。

Conclusion

GroupDocs.Merger for Java は、PDF コレクションの解除、結合、再保護をシンプルに実現する API を提供します。保護検出、解除、統一パスワードでの結合、そしてオプションのパスワードローテーションという 4 つのステップに従うことで、手作業なしで安全な PDF バインダーを自動生成できます。

Next steps:

  • マージ後に PDF メタデータを設定するオプションを調査する(ドキュメント)。
  • ブックマークを保持したまま PDF を結合する方法を学ぶ(API リファレンス)。
  • 完全なサンプルプロジェクトは GitHub で確認できる実装例をチェックする。

Additional Resources