【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());