SpringのWebClientとRestClientの例外処理を解説!タイムアウト・リトライ・フォールバック対応まとめ
Spring Bootを使ったWebアプリケーション開発を、 環境構築から実践まで一通り学びたい方には、 定評のある入門書が参考になります。
Spring Boot 3 プログラミング入門をAmazonで見る※ Amazon広告リンク
生徒
「SpringのWebClientやRestClientでAPI通信してると、たまにタイムアウトとかエラーが出るんですが、ちゃんと対処する方法ってありますか?」
先生
「はい、SpringではWebClientやRestClientで外部APIと通信するときに、タイムアウトや接続エラーなどの例外処理をしっかり実装するのが大切です。さらにリトライやフォールバックの設定を入れると、より堅牢な設計になりますよ。」
生徒
「なるほど……それってどうやって実装するんですか?」
先生
「それでは、WebClientとRestClientそれぞれの例外処理の基本から、リトライ・フォールバックの書き方まで見ていきましょう!」
1. WebClientの例外処理の基本
SpringのWebClientは非同期HTTP通信を行うための強力なツールで、リアクティブな開発に向いています。外部APIのレスポンスが遅い・エラーを返す・通信に失敗するといったケースでは、onErrorResumeやtimeoutを使って例外処理が可能です。
WebClient webClient = WebClient.create();
Mono<String> response = webClient.get()
.uri("https://api.example.com/data")
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(3))
.onErrorResume(throwable -> {
// タイムアウトや接続失敗時の処理
return Mono.just("エラーが発生しました");
});
このようにtimeoutを設定しておけば、APIの応答が遅い場合にもアプリがフリーズせず、onErrorResumeで代替処理ができます。
2. WebClientでのリトライとフォールバック
外部APIが一時的に不安定なときには、retryWhenを使ってリトライ処理を実装できます。リトライしても失敗する場合にはフォールバック処理を定義しておくと安心です。
webClient.get()
.uri("https://api.example.com/data")
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2)))
.onErrorResume(ex -> Mono.just("代替データ"));
retryWhenで最大3回、2秒間隔で再試行し、それでも失敗すればonErrorResumeでフォールバックの処理に入ります。
3. RestClientでの例外処理
RestClientはSpring 6から登場した新しい同期HTTPクライアントで、RestTemplateの後継とされています。こちらでも例外処理を入れることが大切です。
RestClient client = RestClient.create();
try {
String result = client.get()
.uri("https://api.example.com/data")
.retrieve()
.body(String.class);
} catch (HttpClientErrorException | HttpServerErrorException e) {
// 4xx/5xxエラー
System.out.println("HTTPエラー: " + e.getStatusCode());
} catch (ResourceAccessException e) {
// タイムアウトや接続失敗
System.out.println("接続エラー: " + e.getMessage());
}
RestClientでは同期的な実行となるため、try-catchブロックでしっかりエラーハンドリングを記述します。
4. タイムアウトの設定方法(WebClientとRestClient)
タイムアウト設定は、レスポンス待ちの無限ブロックを防ぐために重要です。WebClientではtimeoutをメソッドチェーンに入れ、RestClientでは下層のHTTPクライアント設定で制御します。
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000); // 接続タイムアウト
factory.setReadTimeout(5000); // 読み取りタイムアウト
RestClient client = RestClient.builder()
.requestFactory(factory)
.build();
ネットワークの状況やAPIの特性に合わせて適切に設定しましょう。
5. 例外ログの出力と監視のすすめ
例外が発生したとき、System.out.printlnだけでなく、Loggerを使ってログ出力することで、トラブル発見や監視システムとの連携が可能になります。ログレベルの設定や出力先の制御もできます。
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
try {
// 通常処理
} catch (Exception e) {
logger.error("API通信でエラー", e);
}
SLF4JやLogbackを組み合わせて、アプリ全体の可観測性を向上させましょう。
6. 例外処理を共通化するポイント
複数のAPIクライアントで同じような例外処理を書くと冗長になります。そのため、ExceptionHandlerや共通のユーティリティクラスを活用して、再利用性の高い例外処理コードにしていくと保守性が向上します。
public class ApiFallbackHandler {
public static <T> Mono<T> fallback(Throwable ex, T defaultValue) {
// ログ出力や通知処理も追加可能
return Mono.just(defaultValue);
}
}
このように共通化することで、異なるAPIごとに細かく制御しつつ、例外処理のパターンを統一できます。