REST API エラーハンドリング設計で実務で見るべきポイント

REST API エラーハンドリング設計でステータスコード、エラー形式、ログを整理する図

REST APIのエラーは利用者と運用者で見たい情報が違う

REST API エラーハンドリング設計でまず整理したいのは、誰に何を伝えるエラーなのかです。フロントエンドへ返す内容、外部連携先へ返す内容、運用チームがログで追う内容は同じではありません。

結論から言うと、APIのエラー設計ではHTTPステータスコード、エラーレスポンス形式、内部例外、ログ、監視を分けて決めます。ここを曖昧にすると、画面側の分岐が増えます。さらに、障害時に原因を追えなくなります。

例えば、入力値エラー、権限エラー、対象データなし、外部API障害、DB障害をすべて200で返す設計は危険です。一見フロントエンドで扱いやすそうに見えます。しかし、HTTPクライアント、監視、ログ分析、リトライ制御の意味が崩れます。

そこでこの記事では、実務でREST APIを設計・レビューするエンジニア向けに、エラーハンドリング設計の判断基準を整理します。単なるステータスコード表ではなく、失敗例、レビュー観点、トレードオフまで扱います。

APIエラーハンドリング設計で最初に決めること

まず、個別の例外クラスを作る前に、エラーの分類を決めます。ここが決まっていないと、実装者ごとにcatchの位置やレスポンス形式が変わります。

分類主な扱い
入力エラー必須項目なし、形式不正400系で項目単位の理由を返す
認証・認可エラー未ログイン、権限不足401と403を分ける
業務ルール違反締め済み注文の更新業務コードと表示メッセージを設計する
リソースなし指定IDのデータなし404か業務エラーかを画面要件で判断する
システム障害DB障害、外部API障害詳細は返さずログと監視で追う

この分類は、チームの共通言語になります。例えば「これは400ではなく業務エラーではないか」とレビューで会話できます。その結果、コードレビューが好みの議論になりにくくなります。

HTTPステータスコードを全部200にしない

APIエラー設計でよくある失敗は、すべてのレスポンスを200 OKにして、body内の独自コードだけで成否を表すことです。短期的には実装が楽に見えます。しかし、HTTPの意味を使えなくなります。

ステータスコードの基本は、MDNのHTTP response status codesでも整理されています。REST APIでは、少なくとも400、401、403、404、409、422、500、503の使い分けをチームで決めておくと実務で迷いにくいです。

ステータス使う場面レビュー観点
400リクエスト形式が不正バリデーションエラーと混ぜすぎない
401認証されていないログイン誘導と連動するか
403認証済みだが権限がない権限情報を漏らしていないか
404対象リソースが存在しないID探索の手掛かりを返していないか
409状態競合や一意制約違反再操作で解決できるかを伝える
500想定外のサーバーエラー内部例外の詳細を返していないか

ただし、ステータスコードだけで業務上の意味をすべて表す必要はありません。例えば「申請期限切れ」と「承認済みのため変更不可」は、どちらも業務ルール違反です。一方で、ユーザーへの案内は異なります。そのため、body側には業務エラーコードを持たせます。

エラーレスポンス形式は共通化する

次に、エラーレスポンス形式を決めます。実務では、エンドポイントごとに形式が違うAPIがよく問題になります。フロントエンド側で共通エラーハンドラを作れないためです。

{
  "code": "ORDER_ALREADY_CLOSED",
  "message": "締め済みの注文は変更できません。",
  "details": [
    {
      "field": "orderId",
      "reason": "closed"
    }
  ],
  "traceId": "7f4c2f0a..."
}

例えば、上記のようにcode、message、details、traceIdを持つ形にします。codeはフロントエンドの分岐や問い合わせ時の説明に使えます。messageは表示向けです。detailsは項目エラー向けです。traceIdは運用調査に使います。

なお、HTTP APIのエラー形式にはRFC 9457のProblem Details for HTTP APIsという考え方もあります。既存システムとの互換性がなければ、こうした標準形式を参考にすると独自仕様が増えにくくなります。

内部例外をそのままAPIレスポンスに出さない

実装で避けたいのは、例外メッセージをそのままレスポンスへ返すことです。SQL、テーブル名、外部APIのURL、スタックトレース、個人情報が混ざる可能性があります。

そのため、内部例外とAPIレスポンスは変換層で切り離します。Spring Bootでは、例外ハンドリングをController周辺に集約できます。Spring FrameworkのException Handlerも確認しておくと、設計意図を説明しやすくなります。

@RestControllerAdvice
class ApiExceptionHandler {

  @ExceptionHandler(BusinessException.class)
  ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
    return ResponseEntity
        .status(HttpStatus.CONFLICT)
        .body(ErrorResponse.from(e.code(), e.message()));
  }

  @ExceptionHandler(Exception.class)
  ResponseEntity<ErrorResponse> handleUnexpected(Exception e) {
    log.error("Unexpected API error", e);
    return ResponseEntity
        .status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body(ErrorResponse.systemError());
  }
}

ただし、すべての例外をControllerAdviceだけで片付けると、業務ロジックの意図が見えなくなります。サービス層では、どの業務条件で失敗したのかを明確な例外や結果型で表します。最後に、API境界でHTTPへ変換します。

ログとレスポンスに同じ情報を出さない

エラーレスポンスは利用者向けです。一方で、ログは運用者向けです。この2つを混同すると、情報漏えいか調査不能のどちらかに寄ります。

  • ユーザーへ返す: 操作を続けるために必要な説明
  • ログへ残す: 原因調査に必要な内部情報
  • 監視へ送る: 検知と優先度判断に必要な分類
  • 問い合わせで使う: traceIdやrequestId

例えば、外部決済APIがタイムアウトした場合、ユーザーには「時間をおいて再実行してください」と返します。一方で、ログには外部API名、ステータス、タイムアウト秒数、requestIdを残します。ただし、カード番号やトークンは残しません。

また、業務エラーを毎回errorログにすると監視が荒れます。入力ミスや権限不足はwarnやinfoで十分な場合があります。一方で、DB接続失敗や外部APIの連続失敗はerrorとして通知対象にします。

REST API エラーハンドリング設計の失敗例

ここでは、レビューで指摘されやすい失敗例を整理します。どれも単体テストでは見逃されやすく、結合後や運用後に効いてきます。

失敗例起こる問題改善案
エラー形式がAPIごとに違うフロントエンドの共通処理が壊れる共通ErrorResponseを定義する
例外を握りつぶす障害原因が追えないログとtraceIdを残す
500に業務エラーを混ぜる監視が誤検知する業務エラーは4xxまたは409で表す
詳細すぎるmessageを返す内部構造が漏れる表示文言とログ情報を分ける
リトライ可否が不明二重送信や諦めが起きる一時障害と恒久エラーを分ける

特に外部連携APIでは、リトライ可否が重要です。タイムアウトなら再実行できる場合があります。しかし、バリデーションエラーは再実行しても成功しません。そのため、エラーコードの粒度を運用と画面の両方から決めます。

レビューで見るべきAPIエラー設計の観点

最後に、実務レビューで見る観点をチェックリストにします。実装後ではなく、API仕様を決める段階で確認すると手戻りを減らせます。

  • HTTPステータスコードの使い分けがチームで説明できるか
  • エラーレスポンス形式がAPI全体で統一されているか
  • フロントエンドが共通ハンドラを作れる粒度か
  • 内部例外やスタックトレースを返していないか
  • ログにtraceId、ユーザーID、処理名など調査情報が残るか
  • 業務エラーとシステム障害が監視上で分かれるか
  • リトライしてよいエラーと、修正が必要なエラーを区別できるか

一方で、最初から完璧なエラーコード体系を作ろうとすると重くなります。まずは共通形式、主要ステータス、ログ方針を決めます。その後、業務上よく問い合わせが来るエラーからコードを増やすのが現実的です。

よくある質問

業務エラーは400と409のどちらで返すべきですか?

入力形式の不正なら400が自然です。一方で、リクエスト形式は正しいが現在の業務状態と衝突する場合は409が候補になります。例えば、締め済みデータの更新や楽観ロックの競合です。ただし、既存APIとの一貫性も見ます。

エラーメッセージはバックエンドで作るべきですか?

画面表示文言までバックエンドが完全に持つと、多言語化やUI調整が難しくなります。そのため、業務エラーコードを返し、フロントエンドで表示文言を管理する設計もあります。一方で、外部公開APIではAPI側が説明文を返すほうが利用者に親切です。

ControllerAdviceに集約すれば十分ですか?

十分ではありません。ControllerAdviceはHTTPレスポンスへ変換する場所です。業務上どの失敗なのかは、サービス層の例外設計や戻り値設計で表します。つまり、集約する場所と意味を表す場所を分けることが重要です。

まとめ

REST APIのエラーハンドリング設計では、ステータスコードだけを覚えても足りません。利用者へ返す情報、運用者が追う情報、内部で扱う例外を分けて設計します。

また、エラー形式を共通化すると、フロントエンドの共通処理、監視、問い合わせ対応が安定します。一方で、エラーコードを細かくしすぎると運用負荷が増えます。そのため、実際に分岐や調査で使う粒度から始めるのが現実的です。

Java/Spring BootやREST APIの案件では、エラー設計を説明できるエンジニアが重宝されます。API仕様、例外処理、ログ設計まで含めて案件選びや技術スタックを相談したい方は、現在の経験を整理するところから話せます。

IaC INP PM PMO PMP UX Webディレクター インフラエンジニア キャリアチェンジ フロントエンドエンジニア