サイトアイコン Omomuki Tech

Java `javax.print` API完全ガイド:プリンター制御をマスターしよう

この記事から得られる知識

  • Java Print Service (JPS) API、`javax.print`パッケージの全体像とアーキテクチャ。
  • 利用可能なプリンターを検出し、プログラムで選択する方法。
  • 印刷するデータの種類(テキスト、画像など)を指定する`DocFlavor`の的確な使い方。
  • 部数、用紙サイズ、カラー/モノクロ、両面印刷といった詳細な印刷設定をカスタマイズする方法。
  • 印刷ジョブの実行と、その進捗(成功、失敗、キャンセル)を監視するイベントリスニング。
  • テキストファイルや画像ファイルを印刷する具体的な実装例。
  • サーバーサイドなどGUIのないヘッドレス環境で印刷を行う際の注意点と実装方法。

はじめに: `javax.print`とは?

Javaアプリケーションで印刷機能を実現しようとするとき、多くの開発者が最初に思い浮かべるのがJava Print Service (JPS) APIでしょう。その中核をなすのが`javax.print`パッケージです。このAPIは、J2SE 1.4で導入されて以来、Javaにおける印刷機能の標準として、プラットフォームに依存しない柔軟で強力なプリンター制御機能を提供し続けています。

`javax.print` APIを利用することで、単にテキストや画像を送って印刷するだけでなく、以下のような高度な操作が可能になります。

  • ネットワーク上のプリンターを含む、利用可能な印刷サービスを動的に発見する。
  • プリンターが持つ機能(両面印刷、カラー印刷、ステープル機能など)を問い合わせる。
  • 印刷要求に対して、部数、用紙サイズ、印刷範囲などを細かく指定する。
  • 印刷ジョブの進行状況をリアルタイムで監視し、イベントに応じた処理を実装する。

この記事では、`javax.print` APIの基本的な概念から、具体的な使い方、さらには応用的なテクニックまで、豊富なコード例を交えながら徹底的に解説していきます。サーバーサイドでの帳票印刷から、クライアントアプリケーションでのドキュメント出力まで、あらゆる印刷ニーズに応えるための知識がここにあります。

第1章: `javax.print` APIのアーキテクチャ

`javax.print` APIを効果的に使用するためには、まずそのアーキテクチャと主要な構成要素を理解することが不可欠です。APIは主に4つのパッケージで構成されています。

  • javax.print: APIの中心となるクラスとインターフェースが含まれます。
  • javax.print.attribute: 印刷属性の型や、それらをまとめる属性セットの仕組みを定義します。
  • javax.print.attribute.standard: 用紙サイズや部数といった、標準的な印刷属性を定義したクラスが含まれます。
  • javax.print.event: 印刷ジョブの状態変化などを監視するためのイベントクラスとリスナーインターフェースが含まれます。

Java 9以降のモジュールシステムでは、これらのパッケージはすべて java.desktop モジュールに含まれています。 そのため、`requires java.desktop;` を `module-info.java` に記述する必要があります。

主要なクラスとインターフェース

一般的な印刷処理は、以下の主要な登場人物たちの連携によって成り立っています。

クラス/インターフェース 役割
PrintService 物理的なプリンターや仮想プリンターなど、一つの印刷サービスを表すインターフェース。プリンターの機能に関する情報も保持します。
PrintServiceLookup 利用可能な`PrintService`を検索するための抽象クラスです。 デフォルトのプリンターや、特定の機能を持つプリンターを探すのに使います。
DocFlavor 印刷するデータの「フレーバー(風味)」、つまりデータ形式を指定するクラスです。 MIMEタイプとデータソースのクラス名(例: バイト配列、入力ストリーム)の組み合わせで定義されます。
Doc `DocFlavor`と実際の印刷データ(例: テキスト、画像データ)をカプセル化するインターフェースです。
AttributeSet 印刷設定(属性)の集合を扱うインターフェース群。特に `PrintRequestAttributeSet` がよく使われます。
DocPrintJob 単一の印刷ジョブを表すインターフェース。`PrintService`から取得し、`print()`メソッドで印刷を実行します。

基本的な印刷フロー

`javax.print` APIを使用した印刷処理は、一般的に以下のステップで進められます。

  1. `DocFlavor`の選択: これから印刷しようとしているデータが何か(例: GIF画像、プレーンテキスト)を定義します。
  2. `AttributeSet`の作成: 印刷設定(例: 2部印刷、両面印刷)を準備します。
  3. `PrintService`の検索: 1と2で定義した条件(データ形式と印刷設定)に対応可能なプリンターを探します。
  4. `Doc`の作成: 選択した`DocFlavor`と、実際の印刷データをまとめて`Doc`オブジェクトを生成します。
  5. `DocPrintJob`の取得: `PrintService`から印刷ジョブである`DocPrintJob`を取得します。
  6. 印刷の実行: `DocPrintJob`の`print()`メソッドを呼び出し、`Doc`と`AttributeSet`を渡して印刷を開始します。

この流れを理解することが、`javax.print`を使いこなす第一歩です。次の章から、各ステップを詳しく見ていきましょう。


第2章: プリンターの検索と選択

印刷の最初のステップは、どのプリンターに出力するかを決めることです。`javax.print.PrintServiceLookup`クラスが、この役割を担います。

デフォルトプリンターの取得

最も簡単なのは、OSで「通常使うプリンター」として設定されているデフォルトプリンターを取得する方法です。これは`lookupDefaultPrintService()`静的メソッドで一行で実現できます。

import javax.print.PrintService;
import javax.print.PrintServiceLookup;

public class FindDefaultPrinter {
    public static void main(String[] args) {
        PrintService defaultPrintService = PrintServiceLookup.lookupDefaultPrintService();

        if (defaultPrintService != null) {
            System.out.println("デフォルトプリンターが見つかりました: " + defaultPrintService.getName());
        } else {
            System.err.println("デフォルトプリンターが見つかりませんでした。");
        }
    }
}

すべての利用可能なプリンターを検索

システムにインストールされているすべてのプリンターをリストアップすることも可能です。`lookupPrintServices()`メソッドを使用します。このメソッドの引数には、後述する`DocFlavor`と`AttributeSet`を渡すことができ、特定の条件に合致するプリンターのみをフィルタリングして検索できます。引数を両方`null`にすると、無条件にすべてのプリンターを取得します。

import javax.print.PrintService;
import javax.print.PrintServiceLookup;

public class ListAllPrinters {
    public static void main(String[] args) {
        // 条件を指定せず、すべてのPrintServiceを検索
        PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);

        System.out.println("利用可能なプリンターの数: " + printServices.length);

        for (PrintService printer : printServices) {
            System.out.println(" - " + printer.getName());
        }
    }
}
注意: `lookupPrintServices`が返す配列が空の場合、Java環境から認識できるプリンターが一つもインストールされていないか、設定に問題がある可能性があります。

特定のプリンターを選択する

アプリケーションによっては、ユーザーにプリンターを選択させたり、設定ファイルに基づいて特定のプリンターを使用したりする必要があります。その場合は、取得した`PrintService`の配列をループし、`getName()`メソッドで名前を比較して目的のプリンターを探します。

import javax.print.PrintService;
import javax.print.PrintServiceLookup;

public class FindSpecificPrinter {

    public static PrintService findPrinterByName(String printerName) {
        if (printerName == null || printerName.isEmpty()) {
            return null;
        }
        
        PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);
        
        for (PrintService printService : printServices) {
            if (printService.getName().equalsIgnoreCase(printerName)) {
                return printService;
            }
        }
        
        return null;
    }

    public static void main(String[] args) {
        // 例: "Microsoft Print to PDF" という名前のプリンターを探す
        String targetPrinterName = "Microsoft Print to PDF";
        PrintService foundPrinter = findPrinterByName(targetPrinterName);

        if (foundPrinter != null) {
            System.out.println("目的のプリンターが見つかりました: " + foundPrinter.getName());
        } else {
            System.err.println("'" + targetPrinterName + "' という名前のプリンターは見つかりませんでした。");
        }
    }
}

第3章: 印刷データの準備 (`DocFlavor`と`Doc`)

プリンターが決まったら、次に何を印刷するのかを定義します。`javax.print` APIでは、これを`DocFlavor``Doc`という2つの概念で扱います。

`DocFlavor`とは何か?

`DocFlavor`(ドキュメントフレーバー)は、印刷データの「形式」を厳密に定義するためのクラスです。これは2つの主要な情報から構成されます。

  1. MIMEタイプ: データの種類を示します。例えば、`image/gif`、`text/plain; charset=utf-8`、`application/postscript`などです。
  2. 表現クラス名: Javaプログラム内でデータがどのようなオブジェクトとして表現されているかを示します。例えば、`java.io.InputStream`(入力ストリーム)、`byte[]`(バイト配列)、`java.net.URL`(URLオブジェクト)などです。

例えば、「GIF画像のファイルを、入力ストリームとして印刷したい」場合、`DocFlavor.INPUT_STREAM.GIF`という定義済みの`DocFlavor`オブジェクトを使用します。

`DocFlavor`クラスには、一般的な用途のために多くの静的定数が事前定義されています。以下にその一部を紹介します。

DocFlavor定数 MIMEタイプ 表現クラス 説明
`DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8` `text/plain; charset=utf-8` `byte[]` UTF-8エンコーディングのプレーンテキストをバイト配列で提供
`DocFlavor.INPUT_STREAM.JPEG` `image/jpeg` `java.io.InputStream` JPEG画像をInputStreamで提供
`DocFlavor.URL.GIF` `image/gif` `java.net.URL` GIF画像の場所をURLで指定
`DocFlavor.CHAR_ARRAY.TEXT_PLAIN` `text/plain; charset=us-ascii` `char[]` プレーンテキストを文字配列で提供
`DocFlavor.READER.TEXT_PLAIN` `text/plain; charset=us-ascii` `java.io.Reader` プレーンテキストをReaderで提供
`DocFlavor.SERVICE_FORMATTED.PRINTABLE` `application/x-java-jvm-local-objectref` `java.awt.print.Printable` `java.awt.print.Printable`インタフェースによる描画
`DocFlavor.INPUT_STREAM.AUTOSENSE` `application/octet-stream` `java.io.InputStream` プリンターがデータ形式を自動判別することを期待する場合に使用

プリンター(`PrintService`)ごとにサポートしている`DocFlavor`は異なります。`isDocFlavorSupported(DocFlavor)`メソッドで、目的のプリンターが指定したデータ形式を印刷できるか事前に確認することが重要です。

`Doc`と`SimpleDoc`

`Doc`は、前述の`DocFlavor`と実際の印刷データ本体を一つにまとめるインターフェースです。通常、このインターフェースを直接実装することは稀で、APIが提供するシンプルな実装クラスである`SimpleDoc`を使用します。

`SimpleDoc`のコンストラクタは、以下の3つの引数を取ります。

  1. 印刷データオブジェクト (例: `FileInputStream`, `byte[]`, `String`)
  2. `DocFlavor`オブジェクト
  3. ドキュメント属性セット (`DocAttributeSet`) (通常は`null`でよい)

コード例: テキストファイルから`Doc`オブジェクトを作成する

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.SimpleDoc;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class CreateDocExample {
    public static void main(String[] args) {
        try {
            // 印刷したいテキストファイルへの入力ストリーム
            InputStream inputStream = new FileInputStream("C:/path/to/your/document.txt");

            // データ形式を指定: UTF-8のプレーンテキストを、InputStreamとして提供
            // 実際にはファイルのエンコーディングに合わせて適切なcharsetを指定する必要があります。
            DocFlavor flavor = DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8;
            
            // Docオブジェクトの作成
            Doc doc = new SimpleDoc(inputStream, flavor, null);

            System.out.println("Docオブジェクトが正常に作成されました。");
            System.out.println("Flavor: " + doc.getDocFlavor());
            System.out.println("Data: " + doc.getPrintData());

            // 注意: 実際の印刷処理では、ストリームは印刷ジョブが完了するまで
            // クローズしないようにしてください。
            // inputStream.close(); // ここではまだクローズしない
            
        } catch (FileNotFoundException e) {
            System.err.println("ファイルが見つかりません: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e.getMessage());
        }
    }
}

`DocFlavor`の不一致に注意

`SimpleDoc`を作成する際に渡すデータオブジェクトの型と、`DocFlavor`で指定した表現クラス名が一致していないと、印刷時に`IllegalArgumentException`が発生します。例えば、`FileInputStream`オブジェクトを渡しているのに、`DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8`(表現クラスが`byte[]`)を指定するとエラーになります。


第4章: 印刷設定のカスタマイズ (`AttributeSet`)

印刷部数、用紙の向き、カラー/モノクロ印刷など、印刷の詳細な設定を行うには属性(Attribute)を使用します。これらの属性は`AttributeSet`というコレクションにまとめて管理され、印刷ジョブに渡されます。

`PrintRequestAttributeSet`

印刷要求に関する属性をまとめるために使用するのが`PrintRequestAttributeSet`インターフェースです。 このインターフェースの一般的な実装として`HashPrintRequestAttributeSet`クラスが提供されており、通常はこちらを使用します。

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;

// 印刷要求属性セットのインスタンスを作成
PrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet();

標準的な印刷属性

`javax.print.attribute.standard`パッケージには、IETFのIPP (Internet Printing Protocol) に準拠した、標準的な印刷属性を表現するクラスが多数定義されています。 `add()`メソッドを使って、これらの属性のインスタンスを`AttributeSet`に追加していきます。

以下は、よく使われる属性の例です。

属性クラス 設定内容 設定値の例
`Copies` 印刷部数 `new Copies(3)` (3部印刷)
`Sides` 両面印刷 `Sides.TWO_SIDED_LONG_EDGE` (長辺とじ両面)
`Sides.TWO_SIDED_SHORT_EDGE` (短辺とじ両面)
`Sides.ONE_SIDED` (片面)
`OrientationRequested` 用紙の向き `OrientationRequested.PORTRAIT` (縦向き)
`OrientationRequested.LANDSCAPE` (横向き)
`MediaSizeName` 用紙サイズ `MediaSizeName.ISO_A4` (A4)
`MediaSizeName.JIS_B5` (B5)
`MediaSizeName.NA_LETTER` (レター)
`Chromaticity` カラー設定 `Chromaticity.MONOCHROME` (モノクロ)
`Chromaticity.COLOR` (カラー)
`PageRanges` 印刷ページ範囲 `new PageRanges(1, 3)` (1-3ページ)
`new PageRanges(“1-3, 5, 7-9”)` (1-3、5、7-9ページ)
`JobName` 印刷ジョブ名 `new JobName(“MonthlyReport-2025-07”, null)`

コード例: 属性を設定する

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.*;

public class SetAttributesExample {
    public static void main(String[] args) {
        // 印刷要求属性セットを作成
        PrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet();

        // 属性を追加
        // 1. 印刷部数を2部に設定
        attributeSet.add(new Copies(2));

        // 2. 用紙サイズをA4に設定
        attributeSet.add(MediaSizeName.ISO_A4);

        // 3. 長辺とじ両面印刷を設定
        attributeSet.add(Sides.TWO_SIDED_LONG_EDGE);

        // 4. モノクロ印刷を設定
        attributeSet.add(Chromaticity.MONOCHROME);
        
        // 5. ジョブ名を設定
        attributeSet.add(new JobName("Java-Print-Test", null));

        System.out.println("印刷属性が設定されました。");
        System.out.println("現在の属性数: " + attributeSet.size());
    }
}

プリンターのサポート状況を確認する

すべてのプリンターがすべての属性をサポートしているわけではありません。特定の属性(例えば両面印刷)をプリンターがサポートしているかどうかは、`PrintService`の`isAttributeCategorySupported(Class)`メソッドや`getSupportedAttributeValues()`メソッドで事前に確認することができます。サポートされていない属性を指定して印刷しようとすると、その属性は無視されるか、`PrintException`が発生する可能性があります。


第5章: 印刷ジョブの実行と監視

プリンター、印刷データ、印刷設定が揃ったら、いよいよ印刷を実行します。このプロセスは`DocPrintJob`が担当し、さらに印刷の進行状況を`PrintJobListener`で監視することができます。

`DocPrintJob`の取得と印刷実行

`DocPrintJob`は`PrintService`の`createPrintJob()`メソッドを呼び出して取得します。そして、その`print()`メソッドに`Doc`オブジェクトと`PrintRequestAttributeSet`を渡すことで、印刷ジョブがプリンターに送信されます。

// ... PrintService, Doc, AttributeSet の準備ができている前提 ...

// PrintServiceからDocPrintJobを取得
DocPrintJob printJob = printService.createPrintJob();

try {
    // 印刷を実行
    printJob.print(doc, attributeSet);
    System.out.println("印刷ジョブを送信しました。");
} catch (PrintException e) {
    System.err.println("印刷中にエラーが発生しました: " + e.getMessage());
    // FlavorExceptionやAttributeExceptionなど、より詳細な例外をキャッチすることも可能
}

`print()`メソッドは同期的です。つまり、印刷データがプリンター(またはスプーラー)に完全に送信されるまで、メソッドはブロックされます。処理が完了するとメソッドから戻りますが、これは物理的な紙への印刷が完了したことを意味するわけではない点に注意が必要です。

`PrintJobListener`によるイベント監視

印刷ジョブのライフサイクル(データ転送完了、印刷完了、印刷失敗、キャンセルなど)を追跡したい場合、`PrintJobListener`が役立ちます。`javax.print.event`パッケージに含まれるこのリスナーは、印刷ジョブの状態が変化したときに通知を受け取るための仕組みを提供します。

`PrintJobListener`インターフェースには、以下のメソッドが定義されています。

  • printDataTransferCompleted(PrintJobEvent pje): データ転送が完了したときに呼ばれます。
  • printJobCompleted(PrintJobEvent pje): ジョブが正常に完了したときに呼ばれます。
  • printJobFailed(PrintJobEvent pje): エラーによりジョブが失敗したときに呼ばれます。プリンターの紙切れやインク切れも含まれます。
  • printJobCanceled(PrintJobEvent pje): ジョブがキャンセルされたときに呼ばれます。
  • printJobNoMoreEvents(PrintJobEvent pje): これ以上イベントが発生しないこと(ジョブが終端状態になったこと)を通知します。
  • printJobRequiresAttention(PrintJobEvent pje): プリンターがユーザーの対応(紙の補充など)を必要とするときに呼ばれることがあります。

毎回すべてのメソッドを実装するのは手間なので、空の実装を提供しているアダプタクラス`PrintJobAdapter`を継承するのが一般的です。

コード例: 印刷イベントを監視する

import javax.print.*;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.event.PrintJobAdapter;
import javax.print.event.PrintJobEvent;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class PrintWithListenerExample {

    public static void main(String[] args) throws InterruptedException {
        // デフォルトプリンターを取得
        PrintService printService = PrintServiceLookup.lookupDefaultPrintService();
        if (printService == null) {
            System.err.println("デフォルトプリンターが見つかりません。");
            return;
        }

        // 印刷データを作成 (シンプルなテキスト)
        String textToPrint = "Hello, javax.print world! This is a test.";
        InputStream inputStream = new ByteArrayInputStream(textToPrint.getBytes(StandardCharsets.UTF_8));
        DocFlavor flavor = DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8;
        Doc doc = new SimpleDoc(inputStream, flavor, null);

        // DocPrintJobを作成
        DocPrintJob printJob = printService.createPrintJob();

        // イベントリスナーを登録
        printJob.addPrintJobListener(new MyPrintJobListener());

        try {
            // 印刷を実行
            printJob.print(doc, new HashPrintRequestAttributeSet());
            System.out.println("印刷ジョブの送信を開始しました。完了を待ちます...");
            
            // 非同期で実行されるイベントを待つための簡単な待機処理
            // 本番環境ではより堅牢な同期メカニズムを使用してください
            Thread.sleep(5000); 

        } catch (PrintException e) {
            System.err.println("印刷エラー: " + e.getMessage());
        }
    }
}

class MyPrintJobListener extends PrintJobAdapter {
    @Override
    public void printJobCompleted(PrintJobEvent pje) {
        System.out.println("### イベント受信: 印刷ジョブが正常に完了しました。 ###");
        // ここで後処理などを行う
    }

    @Override
    public void printJobFailed(PrintJobEvent pje) {
        System.err.println("### イベント受信: 印刷ジョブが失敗しました。 ###");
    }

    @Override
    public void printJobCanceled(PrintJobEvent pje) {
        System.out.println("### イベント受信: 印刷ジョブがキャンセルされました。 ###");
    }

    @Override
    public void printDataTransferCompleted(PrintJobEvent pje) {
        System.out.println("### イベント受信: データ転送が完了しました。 ###");
    }

    @Override
    public void printJobNoMoreEvents(PrintJobEvent pje) {
        System.out.println("### イベント受信: これ以上イベントは発生しません。 ###");
    }
}
非同期処理の注意: `print()`メソッド自体は同期的ですが、イベント通知は別スレッドで非同期に行われます。そのため、`main`メソッドがすぐに終了してしまうと、イベントを受け取る前にプログラムが終了してしまいます。上記の例では`Thread.sleep()`で簡易的に待機していますが、実際のアプリケーションでは、ジョブの完了を待つための適切な同期処理(例: `CountDownLatch`や`CompletableFuture`など)を実装する必要があります。

第6章: 実践的な応用例

これまでの章で学んだ知識を組み合わせて、より実践的な印刷処理を実装してみましょう。

応用例1: テキストファイルを印刷する

指定されたテキストファイルを、デフォルトプリンターで印刷する完全なプログラムです。

import javax.print.*;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Copies;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class TextFilePrinter {

    public void print(String filePath) {
        // 1. デフォルトプリンターを取得
        PrintService printService = PrintServiceLookup.lookupDefaultPrintService();
        if (printService == null) {
            System.err.println("デフォルトプリンターが見つかりません。");
            return;
        }
        System.out.println("プリンター: " + printService.getName());

        try (FileInputStream fis = new FileInputStream(filePath)) {
            // 2. DocFlavorを定義
            // ここではプリンターが自動判別することを期待してAUTOSENSEを使用
            DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
            if (!printService.isDocFlavorSupported(flavor)) {
                System.err.println("このプリンターは指定されたデータ形式をサポートしていません: " + flavor);
                // 代替のFlavorを試す (例: TEXT_PLAIN_UTF_8)
                flavor = DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8;
                if (!printService.isDocFlavorSupported(flavor)){
                     System.err.println("代替のデータ形式もサポートされていません。");
                     return;
                }
            }
            
            // 3. Docオブジェクトを作成
            Doc doc = new SimpleDoc(fis, flavor, null);

            // 4. 印刷属性を設定
            PrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet();
            attributeSet.add(new Copies(1));

            // 5. DocPrintJobを作成して印刷
            DocPrintJob printJob = printService.createPrintJob();
            printJob.print(doc, attributeSet);

            System.out.println("'" + filePath + "' の印刷ジョブを送信しました。");

        } catch (FileNotFoundException e) {
            System.err.println("ファイルが見つかりません: " + filePath);
        } catch (PrintException e) {
            System.err.println("印刷エラー: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("予期せぬエラー: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("使用法: java TextFilePrinter <ファイルパス>");
            return;
        }
        new TextFilePrinter().print(args);
    }
}

応用例2: 画像ファイル(JPEG/PNG)を印刷する

画像ファイルの印刷も、`DocFlavor`を適切に選択するだけでテキストファイルとほぼ同様に扱えます。

import javax.print.*;
import javax.print.attribute.HashPrintRequestAttributeSet;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.file.Paths;

public class ImageFilePrinter {

    public void print(String imagePath) {
        PrintService printService = PrintServiceLookup.lookupDefaultPrintService();
        if (printService == null) {
            System.err.println("デフォルトプリンターが見つかりません。");
            return;
        }

        try (FileInputStream fis = new FileInputStream(imagePath)) {
            // ファイルの拡張子に基づいてDocFlavorを選択
            DocFlavor flavor = null;
            String extension = getFileExtension(imagePath);

            if ("jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension)) {
                flavor = DocFlavor.INPUT_STREAM.JPEG;
            } else if ("png".equalsIgnoreCase(extension)) {
                flavor = DocFlavor.INPUT_STREAM.PNG;
            } else if ("gif".equalsIgnoreCase(extension)) {
                flavor = DocFlavor.INPUT_STREAM.GIF;
            } else {
                System.err.println("サポートされていない画像形式です: " + extension);
                return;
            }

            if (!printService.isDocFlavorSupported(flavor)) {
                System.err.println("プリンターがこの画像形式をサポートしていません: " + flavor);
                return;
            }

            Doc doc = new SimpleDoc(fis, flavor, null);
            DocPrintJob printJob = printService.createPrintJob();
            printJob.print(doc, new HashPrintRequestAttributeSet());

            System.out.println("画像ファイル '" + imagePath + "' の印刷ジョブを送信しました。");

        } catch (FileNotFoundException e) {
            System.err.println("ファイルが見つかりません: " + imagePath);
        } catch (PrintException e) {
            System.err.println("印刷エラー: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("予期せぬエラー: " + e.getMessage());
        }
    }

    private String getFileExtension(String fileName) {
        int lastIndexOf = fileName.lastIndexOf(".");
        if (lastIndexOf == -1) {
            return ""; // 拡張子なし
        }
        return fileName.substring(lastIndexOf + 1);
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("使用法: java ImageFilePrinter <画像ファイルパス>");
            return;
        }
        new ImageFilePrinter().print(args);
    }
}

応用例3: PDFファイルの印刷(外部ライブラリとの連携)

`javax.print` APIは、それ自体ではPDFの内容を解釈してレンダリングする機能を持っていません。 `DocFlavor.INPUT_STREAM.PDF` のようなフレーバーは存在しますが、これはプリンター(またはそのドライバー)がPDFを直接解釈できる場合にのみ機能します。一般的なオフィスプリンターではサポートされていないことが多いです。

そのため、JavaからPDFを確実に印刷するには、PDFを画像や`Printable`オブジェクトに変換できる外部ライブラリと組み合わせるのが一般的です。その代表例がApache PDFBoxです。

PDFBoxを利用すると、PDFドキュメントを`PDDocument`オブジェクトとして読み込み、それを`java.awt.print.PrinterJob`で印刷可能な`PDFPageable`や`PDFPrintable`オブジェクトに変換できます。 `PrinterJob`は、`javax.print`の`PrintService`と連携させることが可能です。

この方法は`javax.print`の直接的な機能ではありませんが、JavaにおけるPDF印刷のデファクトスタンダードとなっているため、知っておくと非常に有用です。


第7章: 注意点とトラブルシューティング

ヘッドレス環境での印刷

サーバーサイドアプリケーションやCUI環境など、GUI(グラフィカルユーザーインターフェース)が存在しないヘッドレス環境で`javax.print`を使用する場合、いくつか注意が必要です。

Javaは、`java.awt.headless`というシステムプロパティをチェックして、ヘッドレスモードで動作すべきかどうかを判断します。このプロパティが`true`に設定されていると、AWTやSwingなどのGUI関連の初期化がスキップされ、`HeadlessException`の発生を防ぎます。

`javax.print` API自体は、ヘッドレス環境で動作するように設計されています。 しかし、`PrintService`によっては内部的にUI関連のライブラリに依存している場合があり、問題が発生することもあります。

サーバー環境で実行する場合、Javaアプリケーションの起動時に`-Djava.awt.headless=true`オプションを明示的に指定することが強く推奨されます。

java -Djava.awt.headless=true -jar my-printing-app.jar
印刷ダイアログの呼び出しは、ヘッドレス環境では`HeadlessException`をスローします。サーバーサイドのアプリケーションでは、`ServiceUI`クラスが提供するダイアログ機能や、`java.awt.print.PrinterJob`の`printDialog()`メソッドなどは使用できません。

よくある例外と対処法

例外 主な原因 対処法
`PrintException` 印刷処理における一般的なエラー。 メッセージをよく読み、原因を特定する。多くの場合、より具体的なサブクラスの例外(`FlavorException`など)が根本原因。
`FlavorException` (PrintExceptionのサブクラス) 指定した`DocFlavor`をプリンターがサポートしていない。 `PrintService.isDocFlavorSupported()`で事前にサポート状況を確認する。プリンターがサポートする`DocFlavor`を`getSupportedDocFlavors()`で取得してログに出力し、適切なものを選択する。
`AttributeException` (PrintExceptionのサブクラス) 指定した印刷属性をプリンターがサポートしていない、または値が不正。 `PrintService.isAttributeCategorySupported()`や`getSupportedAttributeValues()`で属性のサポート状況を確認する。
`java.lang.IllegalArgumentException` `SimpleDoc`に渡したデータオブジェクトの型と`DocFlavor`の表現クラス名が一致していない。 `DocFlavor`の定義と、`SimpleDoc`のコンストラクタに渡すデータオブジェクトの型が一致しているか確認する。
`java.awt.HeadlessException` GUIのない環境で、印刷ダイアログなどUI関連の機能を呼び出した。 起動オプションに`-Djava.awt.headless=true`を追加する。UI関連のメソッドを呼び出さないようにコードを修正する。

まとめ

この記事では、Javaの標準印刷APIである`javax.print`パッケージについて、そのアーキテクチャから基本的な使い方、応用例、そして注意点に至るまでを包括的に解説しました。

`PrintService`によるプリンターの検索、`DocFlavor`と`Doc`によるデータ形式の定義、`AttributeSet`を用いた詳細な印刷設定、そして`DocPrintJob`による印刷実行とイベント監視という一連のフローを理解することで、Javaアプリケーションから自由自在に印刷機能を制御できるようになります。

`javax.print` APIは、デスクトップアプリケーションでのドキュメント出力から、サーバーサイドでの大規模な帳票印刷システムまで、幅広いシナリオで活用できる非常に強力なツールです。本記事が、あなたの開発プロジェクトにおける印刷機能の実装の一助となれば幸いです。

モバイルバージョンを終了