Spring Boot 4 の RestClient:同期通信のモダン化
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 にそのまま対応できます。