Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Spring Boot Async Request Handling with DeferredResult: Timeouts and Errors

2026年10月28日に「Dev.to」が公開したITニュース「Spring Boot Async Request Handling with DeferredResult: Timeouts and Errors」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Spring BootのDeferredResultは、時間のかかる処理を非同期で実行し、Webサーバーの応答性を高める仕組みだ。これにより、限られたサーバー資源で多くのリクエストを効率よく処理できる。タイムアウトやエラー時の適切な応答を実装することが重要となる。

ITニュース解説

Spring Bootアプリケーションで、Webサーバーの効率を最大限に高め、ユーザー体験を向上させる技術の一つに非同期リクエスト処理がある。通常、Webサーバーはクライアントからのリクエスト一つ一つに「サーブレットスレッド」と呼ばれる処理担当者を割り当て、その担当者が処理を終えるまで他のリクエストを処理できないという同期的なモデルで動作する。しかし、データベースアクセスや外部API呼び出しなど、時間がかかる処理が発生した場合、このサーブレットスレッドが長時間ブロックされてしまい、結果としてサーバー全体の応答性が低下し、同時に処理できるリクエスト数が減ってしまう問題が浮上する。

この課題を解決するためにSpring Frameworkが提供するのがDeferredResultという仕組みである。DeferredResultは、コントローラーメソッドが長時間かかる操作をサーブレットスレッドをブロックせずに行うためのツールだ。コントローラーメソッドがDeferredResultオブジェクトを返すと、Springはすぐにサーブレットスレッドを解放し、そのスレッドを他のリクエストの処理に利用できるようにする。一方で、実際の時間のかかる作業は別のバックグラウンドスレッドプールで実行され、その処理が完了した時点でDeferredResultオブジェクトに結果を設定する。クライアント(ブラウザなど)はリクエストを送信した後、サーバーからの応答を待つが、サーバー側ではその間に他のリクエストを並行して処理できるため、システム全体のスループットが向上する。これは、まるで受付窓口が、複雑な手続きが必要な客を一時的に別の担当者に引き継ぎ、自分は次の客の対応に移るようなイメージだ。

DeferredResultの基本的な使い方は、コントローラーメソッドでDeferredResult型のオブジェクトを返すことにある。このオブジェクトを作成する際に、処理の最大待機時間を示すタイムアウト値をミリ秒単位で指定できる。例えば、new DeferredResult<>(5000L)とすれば、5秒以内に結果が設定されない場合にタイムアウトする設定となる。タイムアウトが発生した場合に備え、deferredResult.onTimeout()メソッドを使って、タイムアウト時に実行される処理を定義できる。この中でdeferredResult.setErrorResult()を呼び出すことで、タイムアウトした旨をクライアントに通知できる。もしsetErrorResult()が呼び出されない場合、リクエストはタイムアウトしたまま宙ぶらりんになるため、必ず適切なエラー応答を設定することが重要だ。また、deferredResult.onCompletion()メソッドを使えば、処理が成功・失敗にかかわらず、最終的にクライアントにレスポンスが送信される際に実行される処理を定義できる。

非同期処理が成功した場合はdeferredResult.setResult("成功メッセージ")のように結果を設定し、エラーが発生した場合はdeferredResult.setErrorResult("エラーメッセージ")のようにエラー情報を設定する。setErrorResult()は例外オブジェクトだけでなく、文字列などの任意のオブジェクトをエラーとして設定できる。ただし、デフォルトではHTTPステータスコードは200(OK)となるため、もしHTTP 4xxや5xxといったエラーを示すステータスコードを返したい場合は、DeferredResultの型パラメータにResponseEntityを指定し、その中にステータスコードとメッセージを含めて返すように設定する必要がある。例えば、ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("タイムアウトしました")のように利用する。また、deferredResult.onError()メソッドを使うと、バックグラウンド処理中に捕捉されなかった例外が発生した場合に、その例外を処理できる。

Spring 5以降では、DeferredResultの代替としてCompletableFutureをコントローラーメソッドから直接返すことも可能になった。CompletableFutureはJava 8から導入された機能で、非同期処理の連鎖や合成をより関数型プログラミングスタイルで行える点が特徴だ。CompletableFutureでは、orTimeout()メソッドでタイムアウトを、exceptionally()メソッドでエラーハンドリングを行う。DeferredResultが明示的なコールバックメソッドで処理の各段階を制御するのに対し、CompletableFutureはより流暢なAPIで非同期処理を記述できる。どちらを選択するかは、プロジェクトのSpringバージョンや、スレッドプールの管理をより明示的に行いたいか、それともより簡潔なAPIで非同期処理を記述したいかによる。DeferredResultを使用する場合は開発者が専用のスレッドプールを管理する必要があるため、本番環境のワークロードに合わせてきめ細かいチューニングが可能となる。一方、CompletableFutureはデフォルトでForkJoinPoolを使用するが、これもカスタマイズは可能である。

非同期処理を導入する上で最も重要な注意点の一つが、バックグラウンドで動作するスレッドプールの適切な管理である。サーブレットスレッドは解放されるものの、バックグラウンドで無制限にスレッドを生成してしまうと、システムリソース(メモリやCPU)を枯渇させる原因となる。そのため、ExecutorService、特にThreadPoolExecutorを使用して、スレッドの最大数やキューのサイズを制限した、いわゆる「有界なスレッドプール」を構成することが不可欠だ。例えば、コアスレッド数、最大スレッド数、キューのサイズを指定することで、同時に実行されるタスク数を制御できる。もしスレッドプールが飽和し、キューも満杯になった場合、新しいタスクの投入はRejectedExecutionExceptionを引き起こすため、この例外が発生した場合にはクライアントに適切なエラー応答を即座に返す必要がある。

また、DeferredResultを使う際にはいくつかのよくある間違いがある。最も頻繁なのは、バックグラウンド処理の完了後、成功または失敗にかかわらず、必ずdeferredResult.setResult()またはdeferredResult.setErrorResult()のいずれかを呼び出すのを忘れてしまうことだ。これを忘れると、クライアントはサーバーからの応答をいつまでも待ち続け、最終的にクライアント側でタイムアウトエラーが発生する。これは、バックエンドの処理自体は成功していたとしても起こり得る。また、onTimeout()コールバックは、バックグラウンド処理を実行しているスレッドではなく、Springの非同期ディスパッチスレッドで実行されることを理解しておく必要がある。そのため、onTimeout()の中でsetResult()を呼び出しても、すでにタイムアウト状態に入っているため、結果はクライアントに送信されない。onTimeout()は、あくまでタイムアウト時にどのようなエラー応答を返すかを設定するための最後の機会と捉えるべきだ。さらに、非同期タスクの実行時間に対してタイムアウト値を短く設定しすぎると、正しく動作しているにも関わらず常にタイムアウトが発生してしまうため、実際のタスクの実行時間を計測し、余裕を持ったタイムアウト値を設定することが望ましい。DeferredResultは一度結果が設定されると再利用できず、またsetResult()を複数回呼び出しても最初の呼び出しのみが有効となるため、この点にも留意する必要がある。もしDeferredResultのコンストラクタでタイムアウト値を指定しない場合、アプリケーションサーバーのデフォルトの非同期タイムアウト(通常は30秒)が適用されるため、予期せぬ挙動を避けるためにも常に明示的なタイムアウト設定を推奨する。

セキュリティフレームワークとの連携において、Spring SecurityはSecurityContextを非同期スレッドに自動的に伝播する仕組みを持つが、バックグラウンドタスクを起動する前に、認証処理が完了している必要がある点には注意が必要だ。これは、バックグラウンドスレッドからはサーブレットリクエストのコンテキストに直接アクセスできないためである。DeferredResultを返すコントローラーのテストには、MockMvcを使ってエンドポイントを呼び出し、asyncDispatch()を適用することで、非同期処理が完了した後の応答を検証できる。

このように、DeferredResultはSpring Bootアプリケーションで長時間かかるリクエスト処理を効率的に、かつ応答性を損なわずに扱うための強力な手段を提供する。適切なスレッドプールの管理、正確なタイムアウトとエラーハンドリングを組み合わせることで、高負荷な環境下でも安定して動作する、スケーラブルなWebサービスを構築できるようになるだろう。

関連コンテンツ

関連IT用語