概要

コンストラクタの引数が5つ6つと増えていくと、呼び出し側で「何番目の引数が何なのか」が分からなくなります。順序を間違えてもコンパイルが通る型が同じ引数(String が3つ並ぶなど)は特に危険です。Builder パターンは、名前付きメソッドで段階的にフィールドを設定し、最後に build() で不変オブジェクトを生成する構造を作ります。必須フィールドは Builder のコンストラクタで強制し、任意フィールドにはデフォルト値を設定できるため、呼び出し側のコードが自己文書化されます。この記事では HTTP リクエストとメールメッセージを題材に Builder パターンの基本構造を示し、Java 標準ライブラリの StringBuilder や HttpClient.newBuilder との対応関係も確認します。

使いどころ

HTTP リクエストの URL・メソッド・ヘッダー・ボディ・タイムアウトを段階的に設定し、不変のリクエストオブジェクトを生成する

メール送信で宛先(必須)・CC・BCC・件名・本文・添付ファイル(任意)を Builder で組み立てる

検索条件オブジェクト(キーワード・日付範囲・ソート順・ページサイズ)を Builder で構築し、条件の組み合わせを柔軟に表現する

コード例

BuilderPatternSample.java
public class BuilderPatternSample {

    static class HttpRequest {
        private final String url;
        private final String method;
        private final String body;
        private final int timeoutMs;
        private final boolean followRedirect;

        private HttpRequest(Builder builder) {
            this.url = builder.url;
            this.method = builder.method;
            this.body = builder.body;
            this.timeoutMs = builder.timeoutMs;
            this.followRedirect = builder.followRedirect;
        }

        @Override
        public String toString() {
            return "HttpRequest{url=" + url + ", method=" + method
                + ", timeout=" + timeoutMs + "ms}";
        }

        static class Builder {
            private final String url;       // 必須
            private String method = "GET";  // 任意(デフォルト値あり)
            private String body = "";
            private int timeoutMs = 30000;
            private boolean followRedirect = true;

            public Builder(String url) {
                if (url == null || url.isEmpty()) {
                    throw new IllegalArgumentException("URL は必須");
                }
                this.url = url;
            }

            public Builder method(String m) { this.method = m; return this; }
            public Builder body(String b) { this.body = b; return this; }
            public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
            public Builder followRedirect(boolean f) { this.followRedirect = f; return this; }

            public HttpRequest build() { return new HttpRequest(this); }
        }
    }

    public static void main(String[] args) {
        var req = new HttpRequest.Builder("https://api.example.com/users")
            .method("POST")
            .body("{\"name\":\"田中\"}")
            .timeout(5000)
            .followRedirect(false)
            .build();
        System.out.println(req);

        // 最小構成(必須フィールドのみ)
        var minimal = new HttpRequest.Builder("https://api.example.com/health")
            .build();
        System.out.println(minimal);

        // 標準ライブラリの Builder 例
        var sb = new StringBuilder()
            .append("Hello, ").append("Builder").append("!");
        System.out.println(sb.toString());
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

var でメソッドチェーンの記述が簡潔になる。生成対象を record にすれば equals・toString が自動生成される。

Java 17
// Java 17: var で簡潔に
var req = new HttpRequest.Builder("https://api.example.com")
    .method("POST")
    .body("{\"name\":\"田中\"}")
    .timeout(5000)
    .build();

Library Comparison

標準 API(手書き Builder)ビルドロジックに検証やデフォルト値の設定を含めたいとき。生成過程を完全に制御できる。フィールドが多いと Builder クラスの記述量が増える。
Lombok(@Builder)ボイラープレートを排除し、アノテーション一つで Builder を自動生成したいとき。生成コードが見えないため、カスタム検証の追加がやりにくい。チームで Lombok の導入方針を合意しておく必要がある。
Immutables(@Value.Immutable)不変オブジェクトの生成に特化し、Builder と同時に equals・hashCode・toString も自動生成したいとき。アノテーションプロセッサの設定が必要。ビルド環境への影響を事前に確認すること。

注意点

Builder のフィールドを mutable にしたまま build() 後も変更できると、生成済みオブジェクトの不変性が壊れる。build() 後の Builder 再利用を禁止するか、フィールドを final にする

必須フィールドの検証は build() メソッド内で行うのが確実。Builder のコンストラクタで強制する方法もあるが、引数が増えると結局テレスコーピングコンストラクタの問題が再発する

Builder パターンはフィールドが4つ以上ある場合に有効。2〜3フィールドならコンストラクタやファクトリーメソッドで十分な場合が多い

Lombok の @Builder は便利だが、生成されるコードが見えにくい。チーム内で Lombok の採否が決まっていない場合は手書きの Builder から始めるほうが安全

FAQ

Builder とコンストラクタはどう使い分けますか。

フィールドが4つ以上、または任意フィールドが多い場合は Builder が有効です。2〜3フィールドで全て必須なら通常のコンストラクタで十分です。

record に Builder は必要ですか。

record はコンストラクタ引数が全フィールドなので、引数が増えると Builder の恩恵があります。record の中に static な Builder クラスを定義するパターンが実用的です。

Builder パターンと Fluent Interface の違いは何ですか。

Fluent Interface はメソッドチェーンの書き方を指し、Builder はオブジェクト生成のパターンです。Builder は Fluent Interface を使うことが多いですが、概念としては別物です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます