Spring Bootで例外発生時にJSONを返す
Spring BootのREST APIサーバで例外発生時のエラー情報をJSONで返す方法を調べたのでメモです。
やりたいこと
- 例外が発生した場合は常にエラー情報をJSONで返したい。
- 例外の種類によってステータスコードを分けたい。例えば、バリデーションエラーが発生した場合は400、その他は500を返す。
- Spring MVCの本来の振る舞いは変更しない。例えば、認可エラーで401を返す振る舞いは維持する。
実現方法
ErrorController
インタフェースを実装すると、例外時のレスポンスをカスタマイズできる。- キャッチした例外によってステータスコードを変更する。
ステータスコードを指定できる例外クラスを作成します。実際のプロダクトでは、エラーコードやエラーメッセージを含む業務例外クラスを作成するとよいでしょう。
class AppException extends RuntimeException { final int status def AppException(int status, String message) { super(message) this.status = status } def AppException(int status, String message, Throwable cause) { super(message, cause) this.status = status } }
ErrorController
インタフェースの実装クラスを作成します。 ErrorAttributes#getErrorAttributes()
メソッドでエラー情報のMapを取得できます。
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.web.ErrorAttributes import org.springframework.boot.autoconfigure.web.ErrorController import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.context.request.ServletRequestAttributes import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse @RestController @RequestMapping(produces = 'application/json') class AppErrorController implements ErrorController { @Autowired ErrorAttributes errorAttributes @Value('${debug:false}') boolean debug @RequestMapping('/error') Map<String, Object> error(HttpServletRequest request, HttpServletResponse response) { // エラー情報を取得する def servletRequestAttributes = new ServletRequestAttributes(request) def attributes = errorAttributes.getErrorAttributes(servletRequestAttributes, debug) def cause = errorAttributes.getError(servletRequestAttributes) if (cause instanceof AppException) { // 例外に含まれるステータスコードとメッセージを返す response.status = cause.status attributes.status = cause.status attributes.error = HttpStatus.valueOf(cause.status).reasonPhrase attributes.message = cause.message } attributes } @Override String getErrorPath() { '/error' } }
例えば、ステータスコード400を返す場合は下記のような例外を投げます。
@GetMapping('/hello') void hello() { throw new AppException(400, 'Custom Validation Error') }
レスポンスの例
ステータスコードを指定した例外(上記のAppException
)を投げた場合は下記になります。この場合はログにスタックトレースが出力されます。
{ "timestamp":1480254775918, "status":400, "error":"Bad Request", "exception":"example.server.AppException", "message":"Intentionally 400 raised", "trace":"example.server.AppException: ...(debugが有効の場合のみスタックトレースが付く)", "path":"/hello" }
401の場合:
{ "timestamp":1480254600193, "status":401, "error":"Unauthorized", "message":"Bad credentials", "path":"/hello" }
404の場合:
{ "timestamp":1480255037435, "status":404, "error":"Not Found", "message":"No message available", "path":"/hello" }
REST APIクライアントの考慮
以下のように例外処理を設計するとよいでしょう。
- ステータス200でJSONが返ってきた場合、正常
- ステータス200だがJSONを解釈できない場合、共通エラー
- ステータス200以外でJSONが返ってきた場合、エラーコードに応じた例外処理
- ステータス200以外でJSONを解釈できない場合、共通エラー
- 接続失敗の場合、共通エラー
参考文献です。
はじめてのSpring Boot―スプリング・フレームワークで簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槙俊明
- 出版社/メーカー: 工学社
- 発売日: 2016/09
- メディア: 単行本
- この商品を含むブログ (1件) を見る
Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
- 作者: 株式会社NTTデータ
- 出版社/メーカー: 翔泳社
- 発売日: 2016/07/21
- メディア: 大型本
- この商品を含むブログ (1件) を見る