【Spring Boot】エラーハンドリング(REST API)
例外スロー
自作例外クラス
Spring Boot では、自作した例外クラスに@ResponseStatusで HTTP ステータスを設定することができます。
この例外がスローされると、設定したステータスのレスポンスが返されます。
注意点として、非検査例外(RuntimeExceptionおよびサブクラス)を継承する必要があります。
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
public NotFoundexception(String message) {
super(message);
}
}ResponseStatusException
@ResponseStatusExceptionは、例外スロー時に HTTP ステータスを設定することで、そのステータスのレスポンスを返すことができます。
@PostMapping
public void create(@RequestBody @Validated SampleDataParam param, BindingResult result) {
if (result.hasErrors()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
//
}ResponseEntityExceptionHandler
ResponseEntityExceptionHandlerは抽象クラスとなっており、特定の例外がスローされた場合にどのようなレスポンスを返すかが実装されています。
以下の例外が対象になります。
| 例外 | ステータス | 説明 |
|---|---|---|
HttpRequestMethodNotSupportedException | 405 | 定義していない HTTP メソッドでアクセスされた場合の例外 |
HttpMediaTypeNotSupportedException | 415 | サポートしていないContent-Typeを受信した場合の例外appilication/jsonに対し、application/x-www-form-urlencodedで送信したなど |
HttpMediaTypeNotAcceptableException | 406 | 指定したContent-Type以外のデータをレスポンスに設定した場合の例外 |
MissingPathVariableException | 500 | パスパラメーターにおいて、パス定義({id})と参照定義(@PathVariable("id"))が異なる場合の例外 |
MissingServletRequestParameterException | 400 | @RequestParam(required = true)のパラメーターが存在しない場合の例外 |
ServletRequestBindingException | 400 | バインディングに関する例外 |
ConversionNotSupportedException | 500 | Bean プロパティに適したエディターまたはコンバーターが見つからない場合の例外 |
TypeMismatchException | 400 | パラメーターの値が定義した型に変換できない場合などの例外 |
HttpMessageNotReadableException | 400 | @RequestBodyによる変換に失敗した場合の例外 |
HttpMessageNotWritebleException | 500 | @ResponseBodyによる変換に失敗した場合の例外 |
MethodArgumentNotValidException | 400 | DTO によるバリデーションエラーの例外 |
MissingServletRequestPartException | 400 | multipart/form-dataの一部が存在しない場合の例外 |
BindException | 400 | @RequestParam、@PathVariableによるバリデーションエラーの例外 |
AsyncRequestTimeoutException | 503 | 非同期リクエストがタイムアウトした場合の例外 |
各例外の処理は、以下のようなhandleメソッドによって定義されています。
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}すべてのhandleメソッドは、handleExceptionInternal()を実行します。ここでレスポンスの情報が生成されます。
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
}
return new ResponseEntity<>(body, headers, status);
}エラーレスポンスのカスタマイズ
特定の例外が発生した場合に、決まったエラー情報をレスポンスボディに設定することを考えます。
{
"status": 400,
"message": "Invalid Parameter"
}エラークラスの作成
レスポンスボディに設定するエラー情報用のクラスを作成します。
public class ResponseError {
private int status;
private String message;
public ResponseError(int status, String message) {
this.status = status;
this.message = message;
}
}ControllerAdvice
作成したすべての Controller で、共通の例外処理を行いたいと考えます。
このような場合にはControllerAdviceクラスを作成します。
REST API では@RestControllerAdviceをクラスに付与します。
@RestControllerAdvice
public class ApiControllerAdvice {
//...
}エラーレスポンスの設定
例外に関する処理を定義する場合は、上述したResponseEntityExceptionHandlerを継承します。
@RestControllerAdvice
public class ApiControllerAdvice extends ResponseEntityExceptionHandler {
//...
}handleExceptionInternal()をオーバーライドすることで、ResponseEntityExceptionHandlerで定義されている例外の共通処理を実装します。
以下の例ではレスポンスボディとして、作成したResponseErrorを必ず設定するようにしています。
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
ResponseError re = new ResponseError(status.value(), ex.getMessage());
return super.handleExceptionInternal(ex, re, headers, status, request);
}例外毎に個別に設定したい場合は、各handleメソッドをオーバーライドします。
以下はMethodArgumentNotValidExceptionがスローされた場合の処理をオーバーライドしています。
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ResponseError re = new ResponseError(status.value(), ex.getMessage());
return handleExceptionInternal(ex, re, headers, status, request);
}例外の追加
ここまでの設定では、ResponseEntityExceptionHandlerに定義された例外にのみ適用されます。
つまり、最初に説明した自作クラスやResponseStatusExceptionは対象外となっています。
これらの例外でも同様の処理をしたい場合は、@ExceptionHandlerを付与したhandleメソッドを定義します。
例えば、ResponseStatusExceptionに対する処理は以下のように定義します。
@ExceptionHandler(ResponseStatusException.class)
protected ResponseEntity<Object> handleResponseStatus(ResponseStatusException ex, WebRequest request) {
ResponseError re = new ResponseError(status.value(), ex.getMessage());
return handleExceptionInternal(ex, null, new HttpHeaders(), ex.getStatus(), request);
}@ExceptionHandlerの引数には、対象となる例外のクラスを指定します。
デフォルトの例外処理
@ExceptionHandlerを指定していない例外に対しての処理は、handleAll()をオーバーライドすることで定義します。
これを定義していない場合、レスポンスのデータにスタックトレースの内容が含まれてしまいます。
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
ResponseError re = new ResponseError(HttpStatus.INTERNAL_SERVER_ERROR, "予期せぬ例外が起こりました。");
return new ResponseEntity<Object>(re, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}これを定義してしまうと、ログにスタックトレースが出力されなくなるので、別途出力する処理を記述する必要があります。
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
log.error(sw.toString());