Help us understand the problem. What is going on with this article?

SpringBootの@RestControllerで例外処理をする

More than 3 years have passed since last update.

概要

SpringBootで@RestControllerを使っているときに、例外処理をする

やってみる

package com.example.sample;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class HelloExceptionHandler extends ResponseEntityExceptionHandler {

    // 自分で定義したMyExceptionをキャッチする
    @ExceptionHandler(MyException.class)
    public ResponseEntity<Object> handleMyException(MyException ex, WebRequest request) {
        return super.handleExceptionInternal(ex, "handleMyException", null, HttpStatus.BAD_REQUEST, request);
    }

    // SpringBoot内で用意されている例外については、ResponseEntityExceptionHandlerクラスで例外ごとに
    // 専用のメソッドが用意されているのでそれをオーバーライドする
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return super.handleExceptionInternal(ex, "MethodArgumentNotValidException", null, HttpStatus.INTERNAL_SERVER_ERROR, request);
    }

    // すべての例外をキャッチする
    // どこにもキャッチされなかったらこれが呼ばれる
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleAllException(Exception ex, WebRequest request) {
        return super.handleExceptionInternal(ex, "handleAllException", null, HttpStatus.INTERNAL_SERVER_ERROR, request);
    }

    // すべてのハンドリングに共通する処理を挟みたい場合はオーバーライドする
    // ex) ログを吐くなど
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        // 任意の処理

        return super.handleExceptionInternal(ex, body, headers, status, request);
    }
}

以下のコントローラを書いて試してみる

package com.example.sample;

import org.springframework.web.bind.annotation.*;
import org.springframework.http.converter.HttpMessageNotReadableException;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/my")
    public String get() {
        throw new MyException();
    }

    @RequestMapping("/message")
    public String get() {
        throw new HttpMessageNotReadableException("hoge");
    }

    @RequestMapping("/all")
    public String get() {
        throw new RuntimeException();
    }
}

/hello/my にリクエストがきたらMyExceptionを投げられる。
うまくいっていれば、handleMyExceptionが呼ばれるはず

/hello/allにリクエストがきたらRuntimeExceptionが投げられる。
RuntimeExceptionはハンドリングしてないので、handleAllExceptionが呼ばれるはず

$ curl "http://localhost:8080/hello/my"
handleMyException
$ curl "http://localhost:8080/hello/message"
HttpMessageNotReadableException
$ curl "http://localhost:8080/hello/all"
handleAllException

解説

例外ハンドリング用のクラスを作る際は、ResponseEntityExceptionHandlerクラスを継承して@RestControllerAdviceアノテーションをつける。
実際に例外をハンドリングするメソッドを作る際は、@ExceptionHandler(例外クラス.class)アノテーションをつけてあげると、その例外が投げられたときに呼ばれるようになる。引数でその例外オブジェクトを型に指定する。
なので、ハンドルしたい例外ごとにメソッドをつくってハンドリングするのと、未知の例外が投げられたときに備えて最後の砦としてExceptionクラスをキャッチするメソッド(上記のコードでのhandleAllExceptionメソッド)を用意するのがよさそう。
例外処理全てに共通して行いたい処理(ex: ログを吐く等)を入れる場合は、handleExceptionInternalメソッドをオーバーライドするとよい。

補足

Springが用意している例外については、継承しているResponseEntityExceptionHandlerクラス内でそれぞれハンドリングする専用のメソッドが定義されている。ただ、おそらくオーバーライドされる前提のもので、そのままではレスポンス空っぽで返すようになっているのでオーバーライドして使う。(上記のコードでいうhandleMethodArgumentNotValidメソッド)

用意されているメソッドは以下

例外クラス ハンドリングするメソッド
HttpRequestMethodNotSupportedException handleHttpRequestMethodNotSupported
HttpMediaTypeNotSupportedException handleHttpMediaTypeNotSupported
HttpMediaTypeNotAcceptableException handleHttpMediaTypeNotAcceptable
MissingPathVariableException handleMissingPathVariable
MissingServletRequestParameterException handleMissingServletRequestParameter
ServletRequestBindingException handleServletRequestBindingException
ConversionNotSupportedException handleConversionNotSupported
TypeMismatchException handleTypeMismatch
HttpMessageNotReadableException handleHttpMessageNotReadable
HttpMessageNotWritableException handleHttpMessageNotWritable
MethodArgumentNotValidException handleMethodArgumentNotValid
MissingServletRequestPartException handleMissingServletRequestPart
BindException handleBindException
NoHandlerFoundException handleNoHandlerFoundException
AsyncRequestTimeoutException handleAsyncRequestTimeoutException

参考

  • Spring徹底入門 株式会社NTTデータ 2016
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away