Java

SpringのRestClientとは?使い方とRestTemplate非推奨後の移行【Spring Boot 4対応】

RestClientは、Spring Framework 6.1で追加された同期HTTPクライアントです。restClient.get().uri(...).retrieve().body(Pet.class) のように、リクエストの組み立てからレスポンスの型変換までを一続きのメソッドチェーンで書けます。従来のRestTemplateと同じブロッキング動作でありながら、記述はWebClientに近い流れるようなAPIに変わりました。

なお、Visual Studio Codeの拡張機能「REST Client」やChrome拡張のRESTクライアントは、APIを手元から叩くためのツールで別物です。この記事が扱うのはJavaのインターフェース org.springframework.web.client.RestClient で、導入から移行・テストまでをSpring Framework 7.0.8とSpring Boot 4.1の実装に合わせて説明します。

まとめ:RestClientの要点とRestTemplate移行の判断

  • RestClientはSpring Framework 6.1で追加された同期クライアント。7.0のリファレンスはRestTemplateを非推奨と明記し、コードへの@Deprecated付与は開発中の7.1、削除予定は8.0
  • Spring Boot 4.0以降は専用スターター spring-boot-starter-restclient を追加する。3.x系にこのアーティファクトは配布されていない
  • 基本形は get().uri().retrieve().body(型)。ヘッダやステータスコードまで欲しいときは toEntity()、完全に制御したいときは exchange()
  • 4xx・5xxは既定で例外になる。個別処理は onStatus、タイムアウトは spring.http.clients.*、再試行はSpring Framework 7.0の @Retryable
  • テストは MockRestServiceServer.bindTo(RestClient.Builder)(6.1以降)。Boot側では @RestClientTest がこれを自動設定する

使い分けの判断基準、依存関係、リクエストの書き方、エラー処理、移行手順、テストの順に本文で解説します。

RestClient・RestTemplate・WebClientの使い分けと非推奨の現在地

Spring Framework 7.0でRestTemplateが非推奨になった経緯

Spring Framework 7.0のリファレンスは、RestTemplateの節に「As of Spring Framework 7.0, RestTemplate is deprecated in favor of RestClient and will be removed in a future version」という警告を掲げています。ただし7.0.8時点のソースコードでは、RestTemplateクラスに @Deprecated は付いていません。ドキュメント上で非推奨を宣言し、コンパイル警告を出す注釈は次のバージョンに回した形です。

開発中の7.1ブランチでは @Deprecated(since = "7.1", forRemoval = true) が付与され、Javadocに「For removal in 8.0」と記されています。つまり既存コードがビルド警告で埋まるのは7.1へ上げた時点、実際に消えるのは8.0です。2026年8月時点のGAは7.0.8で、Spring Boot 4.1.0が取り込むのもこのバージョンです。

同期はRestClient、非同期とストリーミングはWebClient

クライアント 動作 追加バージョン 現在の位置づけ
RestClient 同期(ブロッキング) 6.1 同期HTTPの標準
RestTemplate 同期(ブロッキング) 3.0 7.0で非推奨・8.0で削除予定
WebClient 非同期・リアクティブ 5.0 非同期/ストリーミング用
HTTP Interface 宣言的(上記を内部利用) 6.0 インターフェース定義で呼ぶ場合

判断基準は単純です。呼び出し元がブロックしてよいならRestClient、ノンブロッキングで並行呼び出しやストリーミングを扱うならWebClientを選びます。表の4行目にあるHTTP Interface(HTTP Service Clients)は、@HttpExchange を付けたインターフェースを定義し、内部でRestClientやWebClientに委譲する書き方です。なお「RestClientで非同期リクエストを送る」と説明する解説を見かけますが、RestClientに非同期実行のAPIはありません。並行実行が要件ならWebClientか仮想スレッドを検討することになります。リアクティブ側の構成はSpring WebFluxとは?ノンブロッキング・リアクティブなWeb開発の基礎【2026年版】で扱っています。

Spring Boot 4での依存関係とRestClient.Builderの受け取り方

spring-boot-starter-restclientの追加

Spring Boot 4.0でスターターが細分化され、ブロッキングHTTPクライアント専用に spring-boot-starter-restclient が追加されました。ビルド定義の説明文は「Starter using Spring’s blocking HTTP clients (RestClient, RestTemplate and HTTP Service Clients)」で、RestClientとHTTPインターフェースの自動設定がここに入っています。Maven Centralでの配布は4.0.0以降で、最新リリースは4.1.0です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-restclient</artifactId>
</dependency>

Gradleなら implementation 'org.springframework.boot:spring-boot-starter-restclient' の1行です。Spring Boot 3.x系にはこのアーティファクトが存在せず、spring-boot-starter-web を入れればRestClientの自動設定も付いてきます。バージョンごとの構成差はSpring Boot 4とは?最新バージョン4.1の変更点・新機能とSpring Boot 3との違いを解説【2026年最新】にまとめています。

RestClient.Builderのコンストラクタ注入

Spring Bootはメッセージコンバータとリクエストファクトリを設定済みの RestClient.Builder をプロトタイプbeanとして用意します。呼び出し側は RestClient.create() で新規に作るのではなく、このビルダーを注入してベースURLだけ足すのが定石です。自分で RestClient.create() を呼ぶと、Jacksonの設定やタイムアウトのプロパティが効かなくなります。

@Service
public class PetService {

    private final RestClient restClient;

    public PetService(RestClient.Builder builder) {
        this.restClient = builder.baseUrl("https://api.example.com").build();
    }

    public Pet fetchPet(long id) {
        return restClient.get().uri("/pets/{id}", id).retrieve().body(Pet.class);
    }
}

アプリ全体へ共通のヘッダやインターセプタを足したい場合は、RestClientCustomizer をbeanとして登録します。個々のサービスでビルダーを触るより、変更点が1か所に集まります。

GET・POST・PUT・DELETEの送信とレスポンス取得

retrieve()とbody()・toEntity()の使い分け

HTTPメソッドに対応する get()post() から始め、uri() でパス、retrieve() でレスポンス取得へ進みます。本文だけ欲しいときは body(型)、ステータスコードやヘッダも見るなら toEntity(型)、本文が空のレスポンスは toBodilessEntity() です。retrieve() は終端操作を呼ぶまでリクエストを実行しません。

Pet pet = restClient.get()
        .uri("/pets/{id}", 42)
        .accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .body(Pet.class);

ResponseEntity<Pet> entity = restClient.get()
        .uri("/pets/{id}", 42)
        .retrieve()
        .toEntity(Pet.class);

URIテンプレートの変数は第2引数以降に渡します。文字列連結でURLを組み立てるとエンコードが自分任せになるため、可変部分は必ずプレースホルダにします。

ボディを送るPOST・PUTと、本文を返さないDELETE

送信側は contentType()body() を挟むだけで、オブジェクトはメッセージコンバータがJSONへ変換します。RestTemplateの postForEntityputdelete のようにメソッド名が分かれていた部分が、同じ形に統一されています。

ResponseEntity<Void> created = restClient.post()
        .uri("/pets")
        .contentType(MediaType.APPLICATION_JSON)
        .body(newPet)
        .retrieve()
        .toBodilessEntity();

restClient.put()
        .uri("/pets/{id}", 42)
        .contentType(MediaType.APPLICATION_JSON)
        .body(updatedPet)
        .retrieve()
        .toBodilessEntity();

restClient.delete()
        .uri("/pets/{id}", 42)
        .retrieve()
        .toBodilessEntity();

PATCHも patch() で同じ書き方になります。「JDKの標準機能ではPATCHが送れない」という注意書きを見かけますが、これは HttpURLConnection を使う SimpleClientHttpRequestFactory(RestTemplateの既定)の制約です。クラスパスに他のHTTPライブラリが無い場合、RestClientが選ぶのはJDKの HttpClient を使う JdkClientHttpRequestFactory なので、PATCHはそのまま通ります。

exchange()でレスポンスを直接扱う場面

ステータスコードごとに戻り値の型を変えたい、レスポンスヘッダを見て後続処理を分けたいといったケースでは exchange() を使い、リクエストとレスポンスを引数に受け取って自分で組み立てます。ここで注意したいのは、exchange() ではステータスハンドラが適用されない点です。4xx・5xxの扱いも自分のコードで書き切る必要があります。

エラーハンドリング・タイムアウト・リトライの設定

onStatusでの4xx・5xx個別処理

既定では、4xxと5xxのレスポンスに対して RestClientException のサブクラスが投げられます。ステータスごとに独自例外へ置き換えるなら onStatus を挟みます。

Pet pet = restClient.get()
        .uri("/pets/{id}", id)
        .retrieve()
        .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
            throw new PetNotFoundException(response.getStatusCode());
        })
        .body(Pet.class);

アプリ全体で同じ変換をするなら、ビルダー側の defaultStatusHandler に寄せます。引数に条件と処理を渡す形と、ResponseErrorHandler を1つ渡す形の両方が用意されており、後者はRestTemplateの setErrorHandler をそのまま持ち込めます。呼び出し箇所ごとに onStatus を書き写すと、扱いの揺れが後から効いてきます。

spring.http.clientsによるタイムアウト指定

Spring Boot 4のHTTPクライアント共通設定は spring.http.clients 接頭辞に集約されました。接続タイムアウトと読み取りタイムアウトのほか、リダイレクト方針とSSLバンドルもここで指定します。

spring.http.clients.connect-timeout=2s
spring.http.clients.read-timeout=5s

外部APIを呼ぶコードでタイムアウトを設定しないままにすると、相手側の遅延がそのままスレッド占有として自分のアプリに伝播し、応答が返らないままリクエストが積み上がります。HTTP Interfaceで呼び先をグループ化している場合は、spring.http.serviceclient.<グループ名>.read-timeout で相手ごとに値を変えられます。

@Retryableでの再試行

Spring Framework 7.0から、再試行の仕組みが本体に入りました。org.springframework.resilience.annotation.Retryable を付けたメソッドが対象で、有効化には設定クラスへの @EnableResilientMethods が必要です。先ほどの PetService.fetchPet に付けると次のようになります。

@Retryable(includes = RestClientException.class, maxRetries = 2, delay = 500, multiplier = 2.0)
public Pet fetchPet(long id) {
    return restClient.get().uri("/pets/{id}", id).retrieve().body(Pet.class);
}

既定値は maxRetries が3、delay が1000ミリ秒、multiplier が1.0(間隔を伸ばさない)です。別プロジェクトのSpring Retryが提供する同名アノテーションは属性が maxAttempts で、初回実行を回数に含めます。importを取り違えると再試行回数が1回ずれるので、パッケージを確認してください。

再試行はAOPプロキシを経由したときだけ働きます。同じクラスの内部から fetchPet を直接呼ぶとプロキシを通らず、注釈が無視されます。呼び出し元が別のbeanであることを確認してください。

RestTemplateからRestClientへ移行する手順と判断基準

移行の入口は RestClient.create(restTemplate) です。既存のRestTemplateが持つリクエストファクトリ、インターセプタ、メッセージコンバータをそのまま引き継いだRestClientが得られるので、認証ヘッダやプロキシ設定を作り直さずに呼び出し部分から書き換えられます。

RestTemplate RestClient
getForObject(url, Pet.class) get().uri(url).retrieve().body(Pet.class)
getForEntity(url, Pet.class) get().uri(url).retrieve().toEntity(Pet.class)
postForEntity(url, req, Void.class) post().uri(url).body(req).retrieve().toBodilessEntity()
exchange(url, method, entity, 型) method(...).uri(url).body(...).retrieve().toEntity(型)
setErrorHandler(handler) defaultStatusHandler(handler)

優先順位は付けたほうが現実的です。7.0の時点ではコンパイル警告すら出ないため、動いているRestTemplateのコードを一斉に書き換える必要はありません。一方で、新規に書くコードでRestTemplateを選ぶ理由はもうありません。8.0での削除に備えるなら、共通のHTTP呼び出しを持つクラスから順に、テストが揃っている箇所だけを移していきます。ResponseErrorHandler を自作している箇所は移行時の書き換え量が読みにくいので、後回しにして構いません。

Spring Framework 8.0がどのSpring Bootに載るかは、サポート期限と合わせて見ておく必要があります。バージョンごとの終了時期はSpring Bootバージョン一覧とサポート期限【2026年7月】最新4.1と3.5 EOL後の選び方で確認できます。

MockRestServiceServerと@RestClientTestによるテスト

RestClient.Builderへのバインド

外部APIを実際に呼ばずにテストする仕組みは、RestTemplate時代から使われてきた MockRestServiceServer がそのまま使えます。Spring Framework 6.1で bindTo(RestClient.Builder) のオーバーロードが追加され、ビルダー単位でモックを差し込めるようになりました。

RestClient.Builder builder = RestClient.builder();
MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build();

server.expect(requestTo("https://api.example.com/pets/42"))
        .andExpect(method(HttpMethod.GET))
        .andRespond(withSuccess("{\"id\":42}", MediaType.APPLICATION_JSON));

Pet pet = new PetService(builder).fetchPet(42);

assertThat(pet.id()).isEqualTo(42);
server.verify();

マッチャは MockRestRequestMatchers、レスポンス生成は MockRestResponseCreators の静的インポートで使います。verify() を呼ばないと、期待したリクエストが飛ばなくてもテストが通ってしまいます。

@RestClientTestでのスライステストとRestTestClientとの違い

Spring Bootでは @RestClientTest がこの組み立てを肩代わりし、対象のサービスとMockRestServiceServerだけを載せた軽いコンテキストを起動します。Spring Boot 4ではテスト用スターター spring-boot-starter-restclient-test が分離されているので、依存に追加してください。

名前が似ている RestTestClient は用途が違います。こちらはSpring Framework 7.0で追加されたインターフェースで、パッケージは org.springframework.test.web.servlet.client。MockMvcやコントローラ、あるいは起動済みのサーバにバインドして、自分が公開するエンドポイントを検証します。外部APIを呼ぶ側のテストに使うものではありません。認証を挟んだエンドポイントを検証する場合の前提はSpring Securityとは?認証・認可の仕組みと基本設定をわかりやすく解説【2026年版】が参考になります。

よくある質問

VS Codeの拡張機能「REST Client」とSpringのRestClientは同じものですか?

別物です。VS Codeの拡張機能は、エディタ上に書いたHTTPリクエストを送ってレスポンスを確認するツールで、Java以外の開発でも使われます。この記事で扱うRestClientは、Spring Frameworkのアプリケーションコードから外部APIを呼ぶためのインターフェースです。検索結果には両方が混在するので、Spring側を探すときは「spring restclient」で絞ると目的の情報にたどり着きやすくなります。

RestTemplateを使い続けるとビルドはいつ壊れますか?

壊れるのは8.0へ上げたときです。7.0.8のコードに @Deprecated は付いていないため現時点で警告は出ず、開発中の7.1で注釈が付いて非推奨警告が出るようになります。CIで警告をエラー扱いにしている場合は、7.1へ上げる前に対象箇所を洗い出しておくと止まりません。

Spring Boot 3でRestClientを使うにはどうしますか?

spring-boot-starter-web を依存に入れれば RestClient.Builder の自動設定が有効になります。専用スターターはMaven Centralでも4.0.0以降しか公開されていないため、3.x系では使えません。3.x系から4.xへ上げる計画があるなら、依存の書き換えでこのスターター名の変更が必要になる点を移行手順に含めておくと漏れません。

RestClientで複数のAPIを並行して呼べますか?

RestClient自体に非同期実行のAPIはないため、呼び出しは1本ずつ直列に走ります。並行させるなら、リアクティブなWebClientを使うか、Java 21以降の仮想スレッド上で複数のRestClient呼び出しを走らせる形になります。ブロッキングのまま並行数を稼ぎたい場合は後者のほうが既存コードの変更は小さく済みます。

Basic認証を付けるにはどう書きますか?

呼び出しごとに付けるなら headers(h -> h.setBasicAuth(user, password)) を挟みます。同じ資格情報を全リクエストで使う場合は、ビルダーに requestInterceptor(new BasicAuthenticationInterceptor(user, password)) を登録すると、呼び出し側のコードから認証の記述を外せます。

関連記事

資料請求

RELATED POSTS 関連記事