Zademy

Spring Boot 4 の RestClient:同期通信のモダン化

spring-boot; spring-framework; restclient; http-client
words 単語

Spring Boot 4.0 は Spring Framework 7 を基盤にしていますが、私が注目しているのは HTTP 通信の設計が根本から変わっている点です。RestTemplate が抱えてきた技術的負債を切り捨て、Java 21+ の並行性モデルに合わせたクライアントが標準になります。

主要新機能と改善

RestClient は Spring Framework 6.1(Spring Boot 3.2)で導入されました。Spring Boot 4 では、同期開発のデフォルトとして定着します。

新しい同期クライアント抽象

RestClient はビルダー式の fluent API を持つ同期 HTTP クライアントです。Spring MVC スタックのアプリケーションでデフォルトとして使われます。

従来の RestTemplate と比べて、私が良いと思う点がいくつかあります。メソッドチェーンでリクエストを組み立てられるので、WebClient と同じ感覚で書けます。onStatus() で HTTP ステータスコードごとのエラーハンドリングを統合できるのも地味に便利です。ヘッダーやクエリーパラメータ、パス変数も API メソッドに直接追加でき、インターセプターもサポートしています。ロギングや認証の処理を挟み込みやすい設計です。

宣言的 HTTP サービスクライアント

Spring Boot 4 で一番面白いのは、宣言的 HTTP サービスクライアントがネイティブで改善されたことです。

外部サービスとの通信を、注釈付きの純粋 Java インターフェースで定義できます。いわゆる「Feign キラー」ですね。手動で RestClient 実装を作る必要がなくなります。

Spring Framework 7 は @ImportHttpServices 注釈を導入しました。Spring Boot 4 の自動設定と組み合わせると、クライアントプロキシを Spring bean として生成する手順がかなりシンプルになります。以前 HttpServiceProxyFactory で書いていた煩雑なコードは消えました。

性能と Project Loom との整合性

RestClient はブロッキングな同期クライアントです。ですが、Project Loom の仮想スレッド(VTs)と組み合わせることで、リアクティブプログラミング(WebClient)に近いスケーラビリティを実現できます。

仮想スレッド並行性

昔は RestTemplate の同期コードが I/O 待ちでプラットフォームスレッドを占有し、スケーラビリティのボトルネックになっていました。

仮想スレッドが I/O 呼び出し(HTTP リクエストなど)を行うと、すぐにパークしてキャリアスレッドを解放します。そのスレッドが別のタスクを実行できるので、ブロッキングコードのまま高い並行性を得られます。

仮想スレッドは 1 つあたり約 1KB と軽量です。プラットフォームスレッドが 1〜8MB 消費することを考えると、オーダーが違います。

効率

シンプルなブロッキングコードで I/O 結合のワークロードを高い並行性で処理できるので、WebClient のリアクティブスタックが持つコード複雑性を避けつつ同等のスケーラビリティに到達できます。

性能設定

spring.threads.virtual.enabled を true にすれば、Spring Boot 4 で仮想スレッドを使うクライアント設定は自動化されます。タイムアウトは spring.http.clients.connect-timeout と spring.http.clients.read-timeout で統一的に管理できます。

新しい構文:RestTemplate との比較と例

構文比較:RestClient vs. RestTemplate

最大の変化は fluent API の採用です。RestTemplate の冗長なテンプレートパターンから離れられます。

クライアントの作成は RestClient.create() だけで済みます。GET リクエストなら restClient.get().uri(url).retrieve().body(String.class) とチェーンで書けます。POST も restClient.post().uri(url).body(request).retrieve().toEntity(T.class) で、何をしているか一目でわかります。

なお、RestTemplate は Spring Framework 6.1 で非推奨になり、Spring Framework 8.0 で削除される予定です。

いくつかの明確で説明された例(RestClient)

設定 Bean 作成

Spring Boot では RestClient.Builder を注入して、ベース URL やデフォルトヘッダーを設定した bean を作るのが基本です。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfiguration {

    @Bean
    public RestClient githubRestClient(RestClient.Builder builder) {
        // すべてのリクエストでベース URL とデフォルトヘッダーを設定
        return builder
            .baseUrl("https://api.github.com")
            .defaultHeader("Accept", "application/json")
            .build();
    }
}

ルート変数とクエリーパラメータ付き GET リクエスト

URI テンプレートに変数を展開し、クエリーパラメータも追加できます。

// 'githubRestClient' が注入済みと仮定

public String getRepositoryInfo(String owner, String repo, boolean verbose) {
    String uriTemplate = "/repos/{owner}/{repo}";

    return githubRestClient.get()
        .uri(uriTemplate, owner, repo) // {owner} と {repo} にマッピング
        .queryParam("verbose", verbose) // ?verbose=true または false を追加
        .retrieve()
        .body(String.class);
}

POST リクエスト(リソース作成)

Java オブジェクト(NewUserDTO など)を自動的に JSON にシリアライズし、レスポンスも型安全に受け取れます。

import org.springframework.http.MediaType;

public User createNewUser(NewUserDTO userData) {
    return githubRestClient.post()
        .uri("/users")
        .contentType(MediaType.APPLICATION_JSON) // Content-Type 設定
        .body(userData) // JSON にシリアライズされるオブジェクト
        .retrieve()
        .body(User.class); // レスポンスを User オブジェクトに逆シリアライズ
}

HTTP ステータスベースのエラーハンドリング

RestClient が汎用 RestClientException をスローする前に、ステータスコードごとのハンドリングを定義できます。これが RestTemplate との一番の差だと思います。

import org.springframework.http.HttpStatus;

public User safeGetUser(long id) {
    return githubRestClient.get()
        .uri("/users/{id}", id)
        .retrieve()
        .onStatus(HttpStatus.NOT_FOUND, (request, response) -> {
            // 404 の特定ハンドリング
            System.out.println("ユーザーが見つかりません、デフォルトを返します");
            // 例外をスローするか、この場合デフォルト値を返す
            throw new CustomResourceNotFoundException("ユーザー ID " + id + " が見つかりません");
        })
        .onStatus(HttpStatus::is5xxServerError, (request, response) -> {
            // 5xx(サーバーエラー)のハンドリング
            throw new RuntimeException("内部サービスエラー。");
        })
        .body(User.class);
}

宣言的 HTTP クライアント(Spring Boot 4)

一番モダンなパターンは @HttpExchange 注釈付きインターフェースを使う方法です。Spring Boot 4 では @ImportHttpServices で自動的にプロキシが注入されます。

// 1. クライアントインターフェース(契約定義)
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

@HttpExchange(url = "https://api.external.com/api/v1") // ベース URL
public interface ExternalApi {
    @GetExchange("/items/{itemId}") // GET https://api.external.com/api/v1/items/{itemId} にマッピング
    Item getItem(@PathVariable String itemId);
}

// 2. サービスでの使用(Spring Boot 4.0 は @ImportHttpServices 設定で自動プロキシ注入)
@Service
public class ItemService {
    private final ExternalApi externalApi;

    // 生成されたプロキシ実装が注入
    public ItemService(ExternalApi externalApi) {
        this.externalApi = externalApi;
    }

    public Item findItem(String id) {
        // ローカルメソッド呼び出しのように感じる
        return externalApi.getItem(id);
    }
}

RestTemplate は Spring Framework 8.0 で消えます。新しく始めるプロジェクトでは RestClient 一択です。サービス間通信には宣言的アプローチ(@HttpExchange)を検討してください。仮想スレッドとの相性も良いので、モダンな高並行 Java にそのまま対応できます。