注文を登録したら画面に500エラーが出た。もう一度押せば通るかもしれない。でも最初の注文が登録されていたら、二重になるかもしれない。
この記事は、Spring Bootアプリで初めて500エラーの調査を担当する人向けです。例外名を探すだけでなく、同じ操作を繰り返してよいか、どこまで処理が進んだかを確認する順序を示します。
更新系の操作は、500だから失敗したとは限らない。
まず対象リクエストを特定し、保存や外部送信の成否を確認します。原因の確定を待たず、分かった影響と次の連絡時点を共有してください。
500は結果であって、故障箇所ではない
HTTP 500は、処理を完了できない予期しない状態をサーバーが検出したことを表します。ステータスだけでは、DB、アプリのコード、応答の変換、外部APIのどこで失敗したかは分かりません。HTTP仕様の定義
例えば注文の保存後に通知送信が失敗した場合、画面は500でも保存が取り消されるかは、トランザクションや外部処理の境界によって変わります。「エラーが出たので何も変わっていない」とは置けません。
最初の確認は、リクエスト・ログ・更新結果の三つ
| 調べるもの | 残す情報 | 次の判断に使うこと |
|---|---|---|
| 操作 | 時刻、URL、HTTPメソッド、対象ID、操作内容 | どの一件を調べるか |
| サーバーログ | リクエストID、例外、原因チェーン、自作コードの行 | どこで止まったか |
| 更新結果 | 保存済みか、外部へ送信済みか、未確認か | 再実行してよいか |
認証トークンや個人情報を丸ごとチケットへ貼る必要はありません。調査担当が原本を参照できる場所を残し、共有文は必要な情報へ絞ります。
画面の時刻から、同じリクエストのログを探す
次は説明用の架空ログです。Spring Bootを実行して採取したものではありません。
14:32:08 ERROR requestId=demo-order-41
operation=createOrder result=failed
java.lang.IllegalStateException: order creation failed
at example.OrderService.create(OrderService.java:52)
Caused by: java.net.SocketTimeoutException: Read timed out
at example.NotificationClient.send(NotificationClient.java:31)このログで分かるのは、注文作成の経路で通知クライアントの読み取りがタイムアウトしたことです。注文が保存されたか、通知先で受信されたかまでは書かれていません。
次にOrderService.createの処理順と、保存・通知の境界を確認します。ログの直前に保存成功らしい記録があっても、最終的なコミットまで完了したかは別途確かめます。権限のある方法で対象の更新結果を照合してください。
「タイムアウトだからもう一回」ではなく、「同じ操作をもう一回した影響が分かるか」で再実行を決めます。
例外ごとに、最初に見る場所を変える
| ログの手がかり | 最初の確認 |
|---|---|
| NullPointerException | 発生行でnullだった値と、その値を返した処理 |
| DB制約違反 | 保存値、制約、既存データ、同時更新 |
| 外部通信のタイムアウト | 呼び出し先、相手の処理結果、再試行の有無 |
| 応答のJSON変換エラー | 戻り値の型や参照関係、保存との処理順 |
| テンプレート処理のエラー | View名、式、Modelへ渡した値 |
アプリが起動していない場合は別の入口です。Bean不足などで起動に失敗しているなら、まず起動ログを確認します。プロキシが500を返している可能性もあるため、アプリ側に対象リクエストの記録がないなら、応答したサーバーを確かめます。
スタックの読み方で止まったら、Caused byから自作コードを探す方法へ進んでください。
原因が未確定でも、第一報は出せる
上の例なら、次のように報告できます。
14:32、注文登録でHTTP 500を一件確認しました。対象リクエストのログでは通知先との通信がタイムアウトしています。注文の保存と通知先の受信結果は未確認のため、同じ注文の再実行は保留しています。更新結果と他の注文への影響を調べ、14:50に状況を共有します。復旧時刻はまだ見積もれていません。
事実、未確認、今の対応、次の連絡が分かれています。「14:50に報告」は「14:50に復旧」と違います。時間になっても結論が出なければ、その時点で分かったことを返します。
全例外をcatchして200を返す修正はしない
画面の500を消すために例外を握りつぶすと、失敗が成功に見えます。入力の誤りや想定した業務エラーはAPIの契約に沿って応答へ変換し、想定外の失敗は調査できる情報を残します。
Spring MVCでは@ExceptionHandlerなどで例外ごとの応答を扱えます。ただし、どのHTTPステータスを返すかは、その例外名だけでなくAPIの契約で決めます。Spring公式の例外ハンドラ
例外の全文を利用者へ返す必要もありません。SQLや内部パスを画面へ露出させず、利用者向けの案内と、権限管理された調査ログを分けます。
修正後は「画面が通る」より先へ確認する
- 元の条件で、期待するHTTP応答になるか。
- 保存されるべきデータだけが保存されるか。
- 失敗後の再実行で、重複や取り残しが起きないか。
- 正常な操作を壊していないか。
500の調査は、例外名を見つけた時点では終わりません。次の一件では、ログを開くのと同時に「どこまで更新されたか」を確認対象へ入れてください。
