SpringでREST APIのエラーレスポンス設計!初心者でもわかるProblem+JSON形式の作り方
生徒
「REST APIでエラーが起きたときに、ちゃんとしたエラーレスポンスを返したいんですが、どう書けばいいですか?」
先生
「最近ではProblem+JSON形式がよく使われています。エラーの構造が決まっているので、クライアント側でも処理しやすいんですよ。」
生徒
「それってSpringでも使えるんですか?独自に作る必要がありますか?」
先生
「Spring Bootでも簡単に実装できます。JSONのエラーレスポンスをカスタムクラスで返せばOKです。実際の作り方を見てみましょう!」
1. REST APIのエラーレスポンス設計とは
REST APIでは、クライアントとサーバー間のやりとりがJSONで行われることが一般的です。そのため、エラーが発生した際もHTTPステータスコードと共に、わかりやすく構造化されたJSONレスポンスを返すことが望ましいです。
特に近年では、RFC7807で定義されたapplication/problem+jsonというフォーマットが推奨されており、標準的なエラー構造として広く利用されています。
2. Problem+JSON形式の基本構造
Problem+JSON形式は、以下のようなフィールドで構成されます。
- type:エラーの種類を示すURL
- title:簡潔なエラーの説明
- status:HTTPステータスコード
- detail:詳細な説明文
- instance:エラーが発生したURI
{
"type": "https://example.com/not-found",
"title": "リソースが見つかりません",
"status": 404,
"detail": "指定されたユーザーIDは存在しません",
"instance": "/users/123"
}
将来を見据えて、+αのスキルを身につけたい方へ
JavaやLinuxを学んでいても、「このままで市場価値は上がるのか」 「キャリアの選択肢を広げたい」と感じる方は少なくありません。
AIを学ぶならアイデミープレミアム3. Spring Bootでカスタムエラークラスを作成する
Spring Bootでは、独自のエラーレスポンスを定義するために、まずはカスタムクラスを用意します。これがProblem+JSONのフォーマットにあたるものになります。
public class ApiError {
private String type;
private String title;
private int status;
private String detail;
private String instance;
// ゲッター・セッター・コンストラクタを追加
}
4. @ExceptionHandlerでエラーレスポンスを返す
次に、Springの@ControllerAdviceと@ExceptionHandlerを使って、例外が発生したときにApiErrorを返すように設定します。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiError> handleUserNotFound(UserNotFoundException ex, HttpServletRequest request) {
ApiError error = new ApiError(
"https://example.com/not-found",
"リソースが見つかりません",
404,
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
5. Content-Typeをapplication/problem+jsonに設定する
RFC7807では、エラーのContent-Typeをapplication/problem+jsonにすることが推奨されています。Springではproduces属性で指定可能です。
@RequestMapping(
value = "/users/{id}",
produces = "application/problem+json"
)
これにより、クライアントが正しくContent-Typeを解釈し、統一された形式でエラーを処理できるようになります。
6. 実際のREST APIのエラー出力例
例えば、存在しないユーザーIDでAPIにアクセスすると、以下のようなエラーレスポンスが返されます。
{
"type": "https://example.com/not-found",
"title": "リソースが見つかりません",
"status": 404,
"detail": "ID=123のユーザーは存在しません",
"instance": "/users/123"
}
このように整ったJSONレスポンスを返すことで、APIの利用者にとってもエラーの原因が明確になります。
7. 複数の例外に対応するには?
アプリケーション内では複数の例外が発生することがあるため、それぞれの例外に応じて@ExceptionHandlerを分けて記述します。
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiError> handleValidation(ValidationException ex, HttpServletRequest request) {
ApiError error = new ApiError(
"https://example.com/validation-error",
"入力値が不正です",
400,
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
これにより、バリデーションエラーや認可エラーなども一貫した形式で返すことができます。
8. より便利にするためのカスタム例外の活用
業務システムでは複雑なエラーが多く発生します。独自の例外クラスを作成して、そこにエラーコードやエラー種別を持たせる設計もおすすめです。
public class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
これと@ExceptionHandlerを組み合わせることで、さらに柔軟なレスポンスを返せます。
9. Spring Boot 3.0以降のProblemDetail活用例
Spring Boot 3.0からは、ProblemDetailというクラスが標準で提供されています。これを使うと、さらにRFC7807に準拠したレスポンスをシンプルに書けます。
@ExceptionHandler(UserNotFoundException.class)
public ProblemDetail handleNotFound(UserNotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
pd.setTitle("ユーザーが見つかりません");
pd.setDetail(ex.getMessage());
pd.setType(URI.create("https://example.com/not-found"));
return pd;
}
これからのバージョンではProblemDetailの活用も視野に入れるとよいでしょう。
まとめ
REST APIにおけるエラーレスポンス設計の重要性
本記事では、Spring Bootを使ったREST API開発において、エラーレスポンスをどのように設計すべきかを、Problem+JSON形式を軸に解説してきました。 REST APIはフロントエンドや外部サービスと連携する前提で使われることが多く、正常系だけでなく異常系の設計がとても重要になります。 エラー時に単純な文字列や曖昧なJSONを返してしまうと、クライアント側での判定や画面表示が複雑になり、保守性も大きく下がってしまいます。
そこで登場するのが、RFCで定義されているProblem+JSON形式です。 エラー内容を「種類」「概要」「HTTPステータス」「詳細メッセージ」「発生箇所」といった共通の構造で表現できるため、 API全体で統一感のあるエラーレスポンスを実現できます。 Spring Bootと非常に相性が良く、初心者でも段階的に導入しやすい点も大きな特徴です。
Spring BootでProblem+JSONを実装する流れ
実装の流れとしては、まずProblem+JSONの構造に対応したエラー用クラスを用意し、 次に@ControllerAdviceと@ExceptionHandlerを使って、例外発生時にそのクラスを返すように設計します。 これにより、コントローラ側のコードを汚すことなく、アプリケーション全体で一貫したエラーハンドリングが可能になります。
HTTPステータスコードとJSONの中身を必ずセットで返すことで、 クライアントは通信レベルと業務レベルのエラーを明確に区別できます。 また、Content-Typeをapplication/problem+jsonに設定することで、 REST APIとしての仕様が明確になり、外部連携時の信頼性も高まります。
エラーレスポンス設計を活かしたサンプル例
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiError> handleBusiness(BusinessException ex, HttpServletRequest request) {
ApiError error = new ApiError(
"https://example.com/business-error",
"業務エラーが発生しました",
400,
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
このようにカスタム例外とエラーレスポンスクラスを組み合わせることで、 業務ロジックに沿ったエラー設計が可能になります。 REST APIの利用者は、返却されたJSONを見るだけでエラーの種類や原因を把握でき、 フロントエンド側の実装やテストもスムーズに進められます。
Spring Boot 3以降のProblemDetailの位置づけ
Spring Bootの新しいバージョンではProblemDetailが標準で提供され、 よりシンプルにRFC7807準拠のエラーレスポンスを返せるようになりました。 これにより、従来のカスタムクラスを使った実装と使い分けながら、 プロジェクトの規模や方針に合った設計を選択できます。 どちらの方法を選ぶ場合でも、エラーレスポンスの考え方自体は共通しており、 構造化されたJSONで返すという点が重要です。
生徒
「REST APIのエラーレスポンスって、今までは何となく作っていましたが、 こうして形式を決めるととても分かりやすくなりますね」
先生
「そうですね。Problem+JSON形式を使うことで、 APIを使う人全員が同じルールでエラーを理解できるようになります」
生徒
「Spring Bootの@ExceptionHandlerを使えば、 コントローラの中がすっきりするのも良いと思いました」
先生
「その通りです。エラーハンドリングを共通化することで、 修正や追加にも強い設計になります」
生徒
「ProblemDetailも含めて、状況に応じて使い分けられるようにしたいです。 REST APIの設計そのものが少し理解できた気がします」
先生
「その理解がとても大切です。 エラーレスポンス設計は、API品質を大きく左右する重要なポイントなので、 ぜひ今後の開発でも意識していきましょう」
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazonアソシエイト・プログラムを利用しています