例外ハンドリング

Webアプリケーションでの例外ハンドリング方法とレスポンスの返却方法について説明します。

Spring Web MVC では、発生した例外を自動的にログ出力し、クライアントにエラーを返却します。

Spring Web MVCがデフォルトでハンドリングする例外 であれば、アプリケーション側で設定や実装を行わなくても、レスポンスに適切なステータスコードが設定されます。記載のない例外については、デフォルトではステータスコードとして500が設定されます。

エラーが返却された場合、 デフォルトで Sping Bootが提供しているエラー画面が表示されます 。エラー画面の動作については server.error プロパティでカスマイズすることができます。プロパティの詳細については Server Properties を参照してください。

また、デフォルトのエラー画面ではなく独自に作成したエラー画面を表示したい場合には、以下の方法があります。

  • ステータスコードに対応するページを templates/error/<status-code>.html として作成する

  • 例外をハンドリング後、任意のページ名を返す

例えば、ステータスコードが 404 NotFound の画面をカスタマイズしたい場合は templates/error/404.html を作成します。

以下のサンプルコードの動作確認環境については、 動作確認環境と依存ライブラリについて を参照してください。

アプリケーション全体の例外ハンドリングをカスタマイズする例

アプリケーション全体で例外に応じた処理が決まっている場合は、@ControllerAdvice アノテーションを設定したクラスで例外ハンドリングを行います。

どの例外を処理するかは、メソッドに設定された@ExceptionHandlerアノテーションの情報により決まります。 また、返却するステータスコードは @ResponseStatus アノテーションに設定します。

この例では、NoResultExceptionが発生した場合に対象データが存在しないことを示すステータスコード404を返します。 クライアントには、404に対応したテンプレート(Thymeleafを使用した場合のデフォルト設定ではtemplates/error/404.html)ページが返されます。

@ControllerAdvice
public class SampleExceptionHandler {
    
    private final Logger logger = LoggerFactory.getLogger(SampleExceptionHandler.class);
    
    // NoResultExceptionが発生した場合には、クライアントにはステータスコード400を返します。
    @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "対象データなし")
    @ExceptionHandler(NoResultException.class)
    public void noResultExceptionHandler(NoResultException e) {
        if (logger.isDebugEnabled()) {
            logger.debug("対象のデータが存在しません", e);
        }
    }
}

注意点として、@ResponseStatusアノテーションのreason属性を指定しなかった場合は、クライアントに返却したいテンプレートのパスをメソッドの戻り値として明示的に指定する必要があります。

サンプル全体は error-handling-sample を参照してください。

個別機能(Controller)で例外をハンドリングする例

アプリケーション全体ではなく個別機能(Controller)で例外をハンドリングし、エラーページを返したい場合があります。 この場合は、Controller内に例外ハンドリング用のメソッドを作成します。

@Controller
public class Users2Controller {

    private final UserDao userDao;
    private final Logger logger = LoggerFactory.getLogger(Users2Controller.class);

    public Users2Controller(final UserDao userDao) {
        this.userDao = userDao;
    }

    @GetMapping("/users2/{id:[0-9]+}")
    @ResponseBody
    public String getUser(@PathVariable Long id) {
        final String name = userDao.getUserName(id);
        return "name->" + name;
    }

    // NoResultExceptionを捕捉して、デフォルトの404ページではなく
    // Controller固有の404ページをクライアントに返します。
    @ExceptionHandler(NoResultException.class)
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public String noResultExceptionHandler(NoResultException e) {
        if (logger.isDebugEnabled()) {
            logger.debug("data not found.", e);
        }
        return "error/custom_404";
    }
}

単純に例外毎にクライアントに返すステータスコードを決めたい場合には、下の例のように例外クラスに ResponseStatusアノテーションを設定することで対応できます。 ただし、ログなどが一切出力されないため上で説明した@ControllerAdviceの使用を推奨します。

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class SampleException2 extends RuntimeException {
}

サンプル全体は error-handling-sample を参照してください。

Serviceなどで送出した例外を業務エラーとして扱い画面にエラーメッセージを表示する

データベースを使用して入力値をチェックする の実装例を参照してください。