RestTemplateはもう古い?RestClient・WebClient・HTTP Interfaceの違いと使い分け【Spring Boot】
Spring Bootから外部APIを呼び出すとき、以前はRestTemplateを使うのが定番でした。
しかし現在のSpringには、次のような選択肢があります。
- RestTemplate
- RestClient
- WebClient
- HTTP Interface
名前だけを見ると、
「RestTemplateはもう使ってはいけないのか?」
「RestClientとWebClientは何が違うのか?」
「HTTP Interfaceは新しいHTTPクライアントなのか?」
と迷いやすいところです。
先に結論を書くと、基本的な使い分けは次のとおりです。
Spring MVCを使った一般的な同期処理では
RestClient、リアクティブ処理やストリーミングではWebClient、API呼び出しをインターフェースとして整理したい場合はHTTP Interfaceが有力です。
既存のRestTemplateを今すぐすべて書き換える必要はありませんが、新規開発ではRestClientなどを優先したほうがよいでしょう。
RestTemplateはもう古いのか
RestTemplateは、Spring Framework 3.0から提供されている同期型HTTPクライアントです。
RestTemplate restTemplate = new RestTemplate();
UserResponse response = restTemplate.getForObject(
"https://example.com/api/users/{id}",
UserResponse.class,
100
);
長年使われてきたため、既存のSpring Bootプロジェクトでは今でもよく見かけます。
ただし、Spring Framework 6.1では、同期型HTTPクライアントとして、より新しいRestClientが追加されました。公式ドキュメントでも、新しい同期HTTPアクセスにはRestClientが案内されています。
さらに、Spring Framework 7.0ではRestTemplateが正式に非推奨となり、将来のバージョンで削除される予定であることが明記されています。
したがって、現在の位置づけは次のように考えると分かりやすいでしょう。
- 既存のRestTemplate:すぐに削除する必要はない
- 新規実装:RestClientまたはWebClientを優先する
- Spring Framework 7以降へ移行:段階的にRestClientへ移行する
- 非同期処理やストリーミング:WebClientを検討する
Spring Boot 3系、つまりSpring Framework 6系を利用しているプロジェクトでは、RestTemplateが突然動かなくなるわけではありません。
「古いから今すぐ全面改修する」というより、改修する箇所や新しく作る機能から少しずつ置き換えるのが現実的です。
RestClient・WebClient・HTTP Interfaceの比較
まずは全体像を整理してみましょう。
| 選択肢 | 通信モデル | 書き方 | 主な用途 |
|---|---|---|---|
| RestTemplate | 同期・ブロッキング | テンプレート形式 | 既存システムの保守 |
| RestClient | 同期・ブロッキング | Fluent API | 一般的なSpring MVCアプリ |
| WebClient | 非同期・ノンブロッキング | ReactiveなFluent API | WebFlux、高並行処理、ストリーミング |
| HTTP Interface | 基盤クライアントによる | Javaインターフェース | APIクライアントの宣言的な定義 |
Spring公式ドキュメントでも、RestClientは同期型、WebClientはノンブロッキングかつリアクティブ、HTTP Service Clientはアノテーション付きインターフェースとして整理されています。
ここで重要なのは、HTTP InterfaceはRestClientやWebClientと完全に同じ層の機能ではないという点です。
HTTP Interfaceの裏側では、RestClientやWebClientが実際のHTTP通信を担当します。
つまり、次のような関係です。
アプリケーション
↓
HTTP Interface
↓
RestClient または WebClient
↓
外部API
RestClientとは
RestClientは、Spring Framework 6.1から利用できる同期型HTTPクライアントです。
RestTemplateと同じように、HTTPレスポンスが返ってくるまで処理を待つブロッキング方式ですが、APIはWebClientに近いメソッドチェーン形式になっています。
UserResponse response = restClient.get()
.uri("/users/{id}", 100)
.retrieve()
.body(UserResponse.class);
RestTemplateよりも、HTTPメソッド、URL、ヘッダー、レスポンス処理の流れが読みやすくなっています。
RestClientとRestTemplateは、リクエストファクトリー、インターセプター、メッセージコンバーターなどの内部基盤を共有しています。ただし、今後の上位レベルの新機能はRestClientを中心に追加されます。
RestClientが向いているケース
次のような場合は、まずRestClientを候補にするとよいでしょう。
- Spring MVCを使用している
- ControllerやServiceが同期処理で実装されている
- JDBCやJPAなどのブロッキング処理が中心
- 外部APIを一般的なリクエスト・レスポンス形式で呼び出す
- RestTemplateから無理なく移行したい
- ReactorのMonoやFluxを導入する必要がない
一般的なSpring Bootの業務アプリケーションであれば、RestClientが最も扱いやすい選択肢になりそうです。
RestClientを使って外部APIを呼び出す
Gradleの依存関係
Spring Bootでspring-boot-starter-webを使用している場合、RestClientを利用するために特別なライブラリを追加する必要はありません。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
RestClientはspring-webに含まれています。
Spring Bootでは、自動構成されたRestClient.BuilderがBeanとして提供されます。公式ドキュメントでも、このBuilderをDIして使用する方法が推奨されています。
application.yml
外部APIのURLをapplication.ymlで管理します。
external-api:
user:
base-url: https://example.com/api
Propertiesクラス
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "external-api.user")
public record UserApiProperties(
String baseUrl
) {
}
RestClientのBean定義
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClient;
@Configuration
@EnableConfigurationProperties(UserApiProperties.class)
public class RestClientConfig {
@Bean
RestClient userRestClient(
RestClient.Builder builder,
UserApiProperties properties
) {
return builder
.baseUrl(properties.baseUrl())
.defaultHeader(
"Accept",
MediaType.APPLICATION_JSON_VALUE
)
.build();
}
}
Spring Bootが提供するRestClient.Builderを利用することで、メッセージコンバーターやHTTPクライアントなど、Spring Boot側の自動設定を活用できます。
レスポンスDTO
package com.example.demo.client.dto;
public record UserResponse(
Long id,
String name,
String email
) {
}
GETリクエスト
package com.example.demo.client;
import com.example.demo.client.dto.UserResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class UserApiClient {
private final RestClient userRestClient;
public UserApiClient(RestClient userRestClient) {
this.userRestClient = userRestClient;
}
public UserResponse getUser(long userId) {
return userRestClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(UserResponse.class);
}
}
HTTPリクエストとしては、次のような通信になります。
GET https://example.com/api/users/100
Accept: application/json
レスポンスが次のJSONだった場合、
{
"id": 100,
"name": "Taro",
"email": "taro@example.com"
}
UserResponseへ自動的に変換されます。
RestClientでPOSTリクエストを送信する
リクエストDTO
package com.example.demo.client.dto;
public record CreateUserRequest(
String name,
String email
) {
}
POSTの実装
public UserResponse createUser(CreateUserRequest request) {
return userRestClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(UserResponse.class);
}
RestTemplateではHttpEntityを組み立てることがありましたが、RestClientではメソッドチェーンの中でヘッダーやBodyを設定できます。
RestClientでエラーハンドリングを行う
外部APIが4xxや5xxを返した場合の処理も定義できます。
public UserResponse getUser(long userId) {
return userRestClient.get()
.uri("/users/{id}", userId)
.retrieve()
.onStatus(
status -> status.value() == 404,
(request, response) -> {
throw new UserNotFoundException(userId);
}
)
.onStatus(
status -> status.is5xxServerError(),
(request, response) -> {
throw new ExternalApiException(
"ユーザーAPIでサーバーエラーが発生しました"
);
}
)
.body(UserResponse.class);
}
APIクライアント全体に共通のエラー処理を適用する場合は、Builder側でdefaultStatusHandlerを設定できます。
@Bean
RestClient userRestClient(
RestClient.Builder builder,
UserApiProperties properties
) {
return builder
.baseUrl(properties.baseUrl())
.defaultStatusHandler(
status -> status.is5xxServerError(),
(request, response) -> {
throw new ExternalApiException(
"外部APIとの通信に失敗しました"
);
}
)
.build();
}
WebClientとは
WebClientは、Spring Framework 5.0から提供されているノンブロッキング・リアクティブ型のHTTPクライアントです。
戻り値には、主にReactorのMonoやFluxを使用します。
Mono<UserResponse> response = webClient.get()
.uri("/users/{id}", 100)
.retrieve()
.bodyToMono(UserResponse.class);
Mono<UserResponse>は、「将来1件のUserResponseが返される処理」を表しています。
実際のHTTP通信が完了するまでスレッドを占有し続けないため、多数の外部通信を同時に処理するケースで効果を発揮します。
Spring公式ドキュメントでは、WebClientの特徴として、ノンブロッキングI/O、Reactive Streamsのバックプレッシャー、高い並行性などが挙げられています。
WebClientが向いているケース
- Spring WebFluxを使用している
- ControllerからServiceまでリアクティブに実装している
- MonoやFluxをそのまま返す
- Server-Sent Eventsを扱う
- ストリーミングレスポンスを扱う
- 多数の外部APIを並行して呼び出す
- 長時間接続するHTTP通信を扱う
反対に、JPAやJDBCを中心とした一般的なSpring MVCアプリケーションで、最後に毎回.block()するのであれば、RestClientのほうがコードは分かりやすくなります。
WebClient自体は同期的に待機して利用することもできますが、ブロッキングしてしまうとノンブロッキングの利点を十分に活用できません。Spring Bootの公式ドキュメントでも、リアクティブアプリケーションではない場合はWebClientの代わりにRestClientを利用できると案内されています。
WebClientの実装例
Gradleの依存関係
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
Spring MVCのアプリケーションから、外部通信だけWebClientを利用することもできます。
次のようにstarter-webとstarter-webfluxの両方が存在する場合、Spring BootはデフォルトではSpring MVCアプリケーションとして自動構成します。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
ただし、通信部分だけのためにリアクティブ関連の依存関係を増やす必要があるのかは検討したほうがよいでしょう。
単純な同期通信であれば、RestClientで十分なことが多いです。
WebClientのBean定義
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
WebClient userWebClient(
WebClient.Builder builder,
UserApiProperties properties
) {
return builder
.baseUrl(properties.baseUrl())
.build();
}
}
Spring BootはWebClient.Builderも自動構成しており、DIしてWebClientを生成する方法が推奨されています。
APIクライアント
package com.example.demo.client;
import com.example.demo.client.dto.UserResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Component
public class ReactiveUserApiClient {
private final WebClient userWebClient;
public ReactiveUserApiClient(WebClient userWebClient) {
this.userWebClient = userWebClient;
}
public Mono<UserResponse> getUser(long userId) {
return userWebClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(UserResponse.class);
}
}
Controller
package com.example.demo.controller;
import com.example.demo.client.ReactiveUserApiClient;
import com.example.demo.client.dto.UserResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class UserController {
private final ReactiveUserApiClient userApiClient;
public UserController(ReactiveUserApiClient userApiClient) {
this.userApiClient = userApiClient;
}
@GetMapping("/users/{id}")
public Mono<UserResponse> getUser(@PathVariable long id) {
return userApiClient.getUser(id);
}
}
ControllerまでMonoを維持することで、WebClientのノンブロッキング処理を活かせます。
複数のAPIを並行して呼び出す
WebClientは、複数の外部APIを並行して呼び出したい場面でも便利です。
public Mono<UserDetailResponse> getUserDetail(long userId) {
Mono<UserResponse> user =
userWebClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(UserResponse.class);
Mono<List<OrderResponse>> orders =
orderWebClient.get()
.uri("/users/{id}/orders", userId)
.retrieve()
.bodyToFlux(OrderResponse.class)
.collectList();
return Mono.zip(user, orders)
.map(tuple -> new UserDetailResponse(
tuple.getT1(),
tuple.getT2()
));
}
ユーザー情報APIと注文履歴APIを順番に待つのではなく、並行して実行できます。
ただし、WebClientに変更するだけで自動的に高速になるわけではありません。
途中でブロッキング処理を挟んだり、JDBCなどの同期処理をイベントループ上で実行したりすると、リアクティブ処理のメリットを損なう可能性があります。
HTTP Interfaceとは
HTTP Interfaceは、外部APIの呼び出しをJavaインターフェースとして宣言できる仕組みです。
Springの公式ドキュメントでは「HTTP Service Client」と表記されています。
@HttpExchange("/users")
public interface UserApi {
@GetExchange("/{id}")
UserResponse getUser(@PathVariable long id);
@PostExchange
UserResponse createUser(@RequestBody CreateUserRequest request);
}
実装クラスを自分で作成しなくても、Springがプロキシを生成してHTTP通信を実行します。
考え方としては、OpenFeignに近い宣言的なクライアントです。
ただし、HTTP Interface単体で通信するわけではありません。実際の通信には、次のいずれかを利用します。
- RestClient
- WebClient
- RestTemplate
Spring公式ドキュメントでも、HttpServiceProxyFactoryにRestClientAdapterやWebClientAdapterを設定して、クライアントプロキシを生成する構成が示されています。
HTTP InterfaceをRestClientで利用する
同期型のSpring MVCアプリケーションでは、HTTP Interfaceの裏側にRestClientを使用する構成が分かりやすいでしょう。
APIインターフェース
package com.example.demo.client;
import com.example.demo.client.dto.CreateUserRequest;
import com.example.demo.client.dto.UserResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
@HttpExchange(
url = "/users",
accept = "application/json"
)
public interface UserApi {
@GetExchange("/{id}")
UserResponse getUser(@PathVariable long id);
@PostExchange(contentType = "application/json")
UserResponse createUser(
@RequestBody CreateUserRequest request
);
}
プロキシのBean定義
package com.example.demo.config;
import com.example.demo.client.UserApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class UserApiConfig {
@Bean
UserApi userApi(
RestClient.Builder builder,
UserApiProperties properties
) {
RestClient restClient = builder
.baseUrl(properties.baseUrl())
.build();
RestClientAdapter adapter =
RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory =
HttpServiceProxyFactory
.builderFor(adapter)
.build();
return factory.createClient(UserApi.class);
}
}
Serviceから利用する
package com.example.demo.service;
import com.example.demo.client.UserApi;
import com.example.demo.client.dto.UserResponse;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserApi userApi;
public UserService(UserApi userApi) {
this.userApi = userApi;
}
public UserResponse getUser(long userId) {
return userApi.getUser(userId);
}
}
Service側から見ると、通常のJavaメソッドを呼び出しているように見えます。
URLの組み立てやretrieve()、body()などの記述がAPIインターフェースに隠蔽されるため、呼び出し側がすっきりします。
HTTP InterfaceをWebClientで利用する
リアクティブ処理にする場合は、戻り値をMonoやFluxにします。
@HttpExchange("/users")
public interface ReactiveUserApi {
@GetExchange("/{id}")
Mono<UserResponse> getUser(@PathVariable long id);
@GetExchange
Flux<UserResponse> getUsers();
}
設定にはWebClientAdapterを使用します。
@Bean
ReactiveUserApi reactiveUserApi(
WebClient.Builder builder,
UserApiProperties properties
) {
WebClient webClient = builder
.baseUrl(properties.baseUrl())
.build();
WebClientAdapter adapter =
WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory =
HttpServiceProxyFactory
.builderFor(adapter)
.build();
return factory.createClient(ReactiveUserApi.class);
}
HTTP Interfaceは同期・非同期を決めるものではありません。
インターフェースの戻り値と、裏側に設定したHTTPクライアントによって、同期型またはリアクティブ型になります。
HTTP Interfaceが向いているケース
次のような場合は、HTTP Interfaceを導入するメリットがあります。
外部APIのエンドポイントが多い
APIごとにクライアントクラスを実装すると、似たようなコードが増えます。
HTTP Interfaceなら、URLやHTTPメソッドをインターフェースにまとめられます。
APIの仕様をコード上で明確にしたい
@GetExchange("/orders/{orderId}")
OrderResponse getOrder(@PathVariable String orderId);
この宣言を見るだけで、HTTPメソッド、パス、引数、レスポンス型が分かります。
呼び出し側をHTTP通信の詳細から切り離したい
Service層では、HTTPヘッダーやレスポンス変換を意識せず、通常のJavaメソッドとして利用できます。
RestClientとWebClientを選択可能にしたい
インターフェースを維持したまま、下位の通信方式をRestClientまたはWebClientにできます。
ただし、APIが1つか2つしかなく、特殊なレスポンス処理や細かいリクエスト制御が多い場合は、直接RestClientを書くほうが分かりやすい場合もあります。
何でもインターフェース化すればよいわけではありません。
RestClientとHTTP Interfaceはどちらを使うべきか
この2つは、どちらか一方しか選べない関係ではありません。
HTTP Interfaceの裏側にRestClientを使用できるため、次のように判断します。
RestClientを直接使う
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(UserResponse.class);
向いているケースは次のとおりです。
- API呼び出し数が少ない
- HTTP通信の流れをコード上で明示したい
- ステータスごとに複雑なレスポンス処理がある
- 動的なURLやヘッダー制御が多い
- 小規模な実装をシンプルに保ちたい
HTTP InterfaceとRestClientを組み合わせる
UserResponse response = userApi.getUser(id);
向いているケースは次のとおりです。
- APIのエンドポイント数が多い
- APIクライアントの定義をまとめたい
- チーム内で呼び出し方を統一したい
- Service層からHTTP通信の詳細を隠したい
- 宣言的なAPIクライアントを作りたい
実務では、まずRestClientを理解し、APIクライアントが大きくなってきたらHTTP Interfaceで整理する流れでもよいでしょう。
RestTemplateからRestClientへ移行する
RestTemplateのコード
ResponseEntity<UserResponse> response =
restTemplate.exchange(
"/users/{id}",
HttpMethod.GET,
HttpEntity.EMPTY,
UserResponse.class,
userId
);
return response.getBody();
RestClientへ変更
return restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(UserResponse.class);
ステータスコードやレスポンスヘッダーも必要な場合は、toEntityを利用できます。
ResponseEntity<UserResponse> response =
restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.toEntity(UserResponse.class);
既存のRestTemplate設定を再利用する
RestClientは、既存のRestTemplateから生成することもできます。
RestClient restClient =
RestClient.create(restTemplate);
Spring公式の移行ガイドでも、最初に既存のRestTemplateからRestClientを生成し、コンポーネント単位で段階的に置き換える方法が案内されています。
移行手順としては、次の流れが安全です。
- 既存のRestTemplateはそのまま維持する
- RestTemplateからRestClientを生成する
- APIクライアント単位で呼び出し部分を置き換える
- インターセプターやタイムアウト設定を確認する
- すべて移行後、RestClient.Builderによる構成へ整理する
一括置換するより、外部API単位でテストしながら変更したほうが安全です。
WebClientへ移行すれば性能が上がるとは限らない
「RestTemplateが古いなら、全部WebClientに変更しよう」と考えるかもしれません。
しかし、WebClientを導入しただけでアプリケーション全体がノンブロッキングになるわけではありません。
例えば、次のコードはWebClientを使っていますが、最後に処理をブロックしています。
UserResponse response = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(UserResponse.class)
.block();
このような使い方が必ず間違いというわけではありませんが、単純な同期通信であれば次のRestClientのほうが意図が明確です。
UserResponse response = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(UserResponse.class);
WebClientのメリットを活かすには、Controller、Service、DBアクセスなどを含めて、リアクティブな処理の流れを維持する必要があります。
そのため、技術的に新しいという理由だけでWebClientを選ぶのではなく、アプリケーション全体の処理モデルに合わせることが重要です。
実務での選び方
迷ったときは、次の順番で判断するとよいでしょう。
Spring MVCの一般的な業務アプリ
RestClient
JPA、JDBC、MyBatis、Domaなどを利用する同期型アプリケーションであれば、まずRestClientを検討します。
Spring WebFluxのリアクティブアプリ
WebClient
Controllerから外部通信までMonoやFluxを維持する場合はWebClientが適しています。
SSEやストリーミング通信
WebClient
レスポンスを逐次処理する場合は、Fluxを扱えるWebClientが有力です。
外部APIのエンドポイントが多い
HTTP Interface
+
RestClientまたはWebClient
API仕様をJavaインターフェースにまとめると、クライアントコードを整理できます。
既存のRestTemplateが大量にある
すぐに全面移行せず、RestClientへ段階的に移行
現在正常に動いている処理を、非推奨になったという理由だけで一括変更するのはリスクがあります。
改修対象や新規機能から置き換えていくほうが現実的です。
最終的な使い分け
最後に、判断基準をまとめます。
| 状況 | 推奨 |
|---|---|
| Spring MVCの新規開発 | RestClient |
| 同期的な外部API呼び出し | RestClient |
| RestTemplateからの移行 | RestClient |
| Spring WebFlux | WebClient |
| 非同期・ノンブロッキング処理 | WebClient |
| SSE・ストリーミング | WebClient |
| 多数のAPIを並行実行 | WebClient |
| APIクライアントを宣言的に書きたい | HTTP Interface |
| APIエンドポイントが多い | HTTP Interface |
| 既存の安定した処理 | RestTemplateを維持しながら段階移行 |
まとめ
RestTemplateは長く利用されてきたHTTPクライアントですが、Spring Framework 7.0では正式に非推奨となりました。
新規開発では、次の考え方を基本にするとよいでしょう。
- 一般的な同期処理にはRestClient
- リアクティブ処理にはWebClient
- API定義を整理するならHTTP Interface
- 既存のRestTemplateは段階的に移行する
特に注意したいのは、WebClientがRestClientの単純な上位互換ではないことです。
RestClientとWebClientでは、同期型とリアクティブ型という処理モデルそのものが異なります。
Spring MVCを使った一般的なAPIサーバーで、JDBCやJPAなどの同期処理が中心なら、無理にWebClientを導入せずRestClientを選ぶのが分かりやすいでしょう。
そのうえで、外部APIの数が増えてきた場合には、HTTP InterfaceとRestClientを組み合わせることで、見通しのよいAPIクライアントを構築できます。
是非フォローしてください
最新の情報をお伝えします
