Spring Bootの共通エラーレスポンス設計:Problem+JSON風フォーマットを作る
Spring Bootを使ったWebアプリケーション開発を、 環境構築から実践まで一通り学びたい方には、 定評のある入門書が参考になります。
Spring Boot 3 プログラミング入門をAmazonで見る※ Amazon広告リンク
生徒
「Spring Bootでエラーメッセージを統一した形で返す方法ってありますか?」
先生
「良い視点ですね。REST APIの開発では、共通のエラーレスポンスを設計しておくことで、利用者がとても理解しやすくなります。」
生徒
「なるほど!具体的にはどんなフォーマットが使われてるんですか?」
先生
「最近では、Problem+JSON形式という標準に沿ったフォーマットを参考にした設計がよく使われています。それでは、実装のポイントを一緒に見ていきましょう。」
1. Spring Bootで共通エラーレスポンスを作る理由とは?
Spring BootのREST APIでは、バリデーションエラーや認可エラー、内部サーバーエラーなど様々なエラーが発生します。これらをすべてバラバラの形式で返してしまうと、フロントエンド側やクライアント側が扱いにくくなります。
そこで登場するのが「共通エラーレスポンス設計」です。これにより、すべてのエラーが統一された形式で返され、API利用者はエラーをパースしやすくなります。
その中でも注目されているのが「Problem+JSON」風のフォーマットです。RFC7807という仕様に基づいており、エラー情報をtype・title・status・detail・instanceといった項目で表現します。
2. Problem+JSON風フォーマットの基本構造
Problem+JSON形式は、次のようなJSON構造でエラー情報を返すことを前提としています:
{
"type": "https://example.com/errors/not-found",
"title": "Resource Not Found",
"status": 404,
"detail": "指定されたユーザーが見つかりません。",
"instance": "/api/users/123"
}
この構造のメリットは、API利用者がstatusコードだけでなく、typeやtitleなどでエラーの種類や発生場所を把握しやすくなる点にあります。
3. Spring Bootで共通エラーのレスポンスクラスを作成
まずは、共通のレスポンスとして使うクラスを作成します。Javaで@Builderや@Getterを使えば、簡単に定義できます。
public class ApiError {
private String type;
private String title;
private int status;
private String detail;
private String instance;
// コンストラクタやgetter/setterはLombokでもOK
}
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク
4. @ControllerAdviceを使って例外をグローバルにハンドリング
Spring Bootで例外を一元的に扱うには、@ControllerAdviceを使います。これを使うことで、全コントローラに対する例外処理を共通化できます。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex, HttpServletRequest request) {
ApiError error = new ApiError();
error.setType("https://example.com/errors/not-found");
error.setTitle("リソースが見つかりません");
error.setStatus(HttpStatus.NOT_FOUND.value());
error.setDetail(ex.getMessage());
error.setInstance(request.getRequestURI());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 他の例外も同様に追加可能
}
5. 実際のレスポンス例
たとえば、存在しないユーザーIDを指定してリクエストした場合、次のようなエラーが返されます。
{
"type": "https://example.com/errors/not-found",
"title": "リソースが見つかりません",
"status": 404,
"detail": "ユーザーIDが存在しません。",
"instance": "/api/users/999"
}
これにより、フロントエンド側はレスポンスのstatusやtypeを見て、適切な処理(アラート表示や遷移)を実装できます。
6. Problem+JSON形式に合わせたContent-Typeの指定
RFC7807に準拠するなら、Content-Typeヘッダーにapplication/problem+jsonを指定します。
Spring BootでContent-Typeを返すには、レスポンスエンティティに設定を加えます。
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.valueOf("application/problem+json"))
.body(error);
7. 独自のバリデーションエラーや認可エラーにも対応
MethodArgumentNotValidExceptionなどのバリデーションエラーも、Problem+JSON形式で返すことで、クライアントはdetailの内容を元にフィールドごとのエラーメッセージを表示できます。
また、AccessDeniedExceptionやAuthenticationExceptionに対しても、適切なstatusやtitleを返すように設計しておくと、セキュリティ面での制御がしやすくなります。
8. Spring BootでAPIエラーを美しく扱うコツ
Spring Bootでの共通エラーレスポンス設計は、API開発において非常に重要な要素です。Problem+JSON風の統一フォーマットを用いることで、APIの使いやすさや保守性が飛躍的に向上します。
実装はシンプルですが、利用者にとっては大きなメリットとなるため、ぜひ導入を検討してみてください。
まとめ
Spring Bootにおける共通エラーレスポンス設計の重要性
この記事では、Spring BootでREST APIを開発する際に欠かせない「共通エラーレスポンス設計」について、 Problem+JSON風フォーマットを例に詳しく解説してきました。 API開発では正常系のレスポンスだけでなく、エラー発生時にどのような情報を返すかが非常に重要になります。 エラーレスポンスの形式がバラバラだと、フロントエンドや外部クライアントは実装のたびに個別対応が必要になり、 開発効率や保守性が大きく低下してしまいます。
その点、Problem+JSONを参考にした共通フォーマットを採用することで、
エラーの種類、HTTPステータス、詳細メッセージ、発生箇所といった情報を
一貫した構造で返せるようになります。
Spring BootとSpring MVCの仕組みを活かし、
@ControllerAdviceを使って例外処理を集約することで、
API全体のエラーハンドリングを整理された形で実装できるのが大きな特徴です。
Problem+JSON風フォーマットがもたらすメリット
Problem+JSON風のエラーレスポンスでは、
type、title、status、detail、instance
といった項目を持たせることで、
人間にもプログラムにも理解しやすいエラー情報を表現できます。
特にstatusとtypeを組み合わせることで、
クライアント側はエラー内容を機械的に判定しやすくなります。
また、エラーメッセージをdetailに集約しておくことで、
フロントエンド側ではユーザー向けの表示を柔軟に制御できます。
さらに、instanceにリクエストURIを含めることで、
ログ解析や障害調査の際にも役立つ情報を残すことができます。
こうした設計は、規模の大きなAPIや複数人で開発するプロジェクトほど効果を発揮します。
Spring Bootでの実装ポイントを振り返る
実装面では、まず共通で利用するエラーレスポンス用のクラスを定義し、
それを@ControllerAdvice内で組み立てて返却する流れが基本となります。
例外ごとにHTTPステータスやtypeを切り替えることで、
リソース未存在エラー、バリデーションエラー、認可エラーなどを明確に区別できます。
さらに、Content-Typeにapplication/problem+jsonを指定することで、
Problem+JSON形式に近いレスポンスとして扱えるようになります。
このように、Spring Bootの標準機能を活用するだけで、
APIとして完成度の高いエラーハンドリングを実現できる点は大きな魅力です。
まとめとしてのサンプルプログラム
最後に、この記事の内容を振り返るため、 共通エラーレスポンスを返す基本的な構成のサンプルコードを確認してみましょう。 記事内で使用してきたクラス構成や書き方と同じ形式になっています。
@ControllerAdvice
public class SummaryExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleException(Exception ex, HttpServletRequest request) {
ApiError error = new ApiError();
error.setType("https://example.com/errors/internal");
error.setTitle("内部サーバーエラー");
error.setStatus(500);
error.setDetail(ex.getMessage());
error.setInstance(request.getRequestURI());
return ResponseEntity
.status(500)
.contentType(MediaType.valueOf("application/problem+json"))
.body(error);
}
}
このような構成を用意しておけば、 どのような例外が発生しても一定のフォーマットでエラー情報を返せるようになります。 API利用者にとっても理解しやすく、 開発者にとっても拡張しやすい設計となるため、 Spring BootでREST APIを開発する際にはぜひ取り入れておきたい考え方です。
生徒
「エラーレスポンスって、今まであまり意識していませんでしたが、 共通化するとこんなにメリットがあるんですね。」
先生
「そうですね。APIは使われてこそ価値があります。 エラーがわかりやすいAPIは、それだけで使いやすいと言えます。」
生徒
「Problem+JSON風の形式なら、 ステータスやエラー内容を一目で判断できそうです。」
先生
「その理解で正しいです。 Spring BootとControllerAdviceを組み合わせれば、 きれいな共通エラーレスポンスを無理なく実装できますよ。」
生徒
「これからAPIを作るときは、 最初にエラーレスポンス設計も考えるようにしてみます。」