[JAVA] Use @ControllerAdvice, @ExceptionHandler, HandlerExceptionResolver in Spring Boot to catch exceptions

Overview

--Catch exceptions that occur in controller class in Spring Boot application -In the class with @ControllerAdvice, catch each exception class with the method with @ExceptionHandler --Exceptions that are not handled by the method with @ExceptionHandler are caught by the class that implements HandlerExceptionResolver.

This operation check environment

Source code list

├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── my
        │               ├── MyApplication.java
        │               ├── MyController.java
        │               ├── MyControllerAdvice.java
        │               ├── MyException.java
        │               └── MyHandlerExceptionResolver.java
        └── resources
            └── templates
                └── myview.html

MyApplication.java

Spring Boot startup class.

package com.example.my;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

  public static void main(String[] args) {
    SpringApplication.run(MyApplication.class, args);
  }
}

MyException.java

A simple exception class prepared for this operation check.

package com.example.my;

public class MyException extends Exception {
}

MyController.java

A controller class that handles routing. Raise a MyException exception when accessing http: // localhost: 8080 / myexception. Raise an Exception exception when accessing http: // localhost: 8080 / exception.

package com.example.my;

import org.springframework.boot.SpringApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyController {

  public static void main(String[] args) {
    SpringApplication.run(MyController.class, args);
  }

  @RequestMapping("/")
  public ModelAndView handleTop(ModelAndView mav) {
    mav.addObject("mymessage", "Hello, world.");
    mav.setViewName("myview");
    return mav;
  }

  @RequestMapping("/myexception")
  public ModelAndView handleMyException(ModelAndView mav) throws MyException {
    throw new MyException();
  }

  @RequestMapping("/exception")
  public ModelAndView handleException(ModelAndView mav) throws Exception {
    throw new Exception();
  }
}

MyControllerAdvice.java

MyException A class for catching exceptions. Annotate the class with @ControllerAdvice. The @ExceptionHandler annotation is added to the method for catching the exception, and MyException.class is specified. Reference: [ExceptionHandler \ (Spring Framework 5 \ .1 \ .9 \ .RELEASE API )](https://docs.spring.io/spring/docs/5.1.9.RELEASE/javadoc-api/org/springframework /web/bind/annotation/ExceptionHandler.html)

package com.example.my;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class MyControllerAdvice {

  @ExceptionHandler({MyException.class})
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ModelAndView handleMyException(Exception e, WebRequest req) {
    System.out.println("MyControllerAdvice#handleMyException");
    ModelAndView mav = new ModelAndView();
    mav.addObject("myerror", "MyControllerAdvice#handleMyException");
    mav.setViewName("myview");
    return mav;
  }
}

MyHandlerExceptionResolver.java

A class for catching exceptions that @ExceptionHandler does not handle. Implements the HandlerExceptionResolver interface. Annotate the class with @Component to register it in the DI container as a bean. Reference: [HandlerExceptionResolver \ (Spring Framework 5 \ .1 \ .9 \ .RELEASE API )](https://docs.spring.io/spring/docs/5.1.9.RELEASE/javadoc-api/org/springframework /web/servlet/HandlerExceptionResolver.html)

package com.example.my;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    System.out.println("MyHandlerExceptionResolver#resolveException");
    System.out.println(handler.getClass());
    System.out.println(handler);
    ModelAndView mav = new ModelAndView();
    mav.addObject("myerror", "MyHandlerExceptionResolver#resolveException");
    mav.setViewName("myview");
    mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
    return mav;
  }
}

myview.html

Thymeleaf template file for HTML output. Display information such as errors.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:if="${myerror}">
    <div>Error: <span th:text="${myerror}"></span></div>
</div>
<div th:if="${mymessage}">
    <div>Message: <span th:text="${mymessage}"></span></div>
</div>
</body>
</html>

pom.xml

Configuration file for building with Maven.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <groupId>com.example</groupId>
  <artifactId>my</artifactId>
  <version>0.0.1</version>
  <name>my</name>
  <description>My project for Spring Boot</description>

  <properties>
    <java.version>11</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Creating a JAR file and launching Spring Boot

Generate a JAR file with Maven's mvn package command.

$ mvn package

Specify the generated JAR file and start the Web server by Spring Boot with the java command.

$ java -jar target/my-0.0.1.jar

Access with curl and check the behavior

http://localhost:8080/myexception

Access with curl. You can see that it is handled by the handleMyException method of the MyControllerAdvice class.

$ curl http://localhost:8080/myexception
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div>Error: <span>MyControllerAdvice#handleMyException</span></div>
</div>

</body>
</html>

Spring Boot Server side output.

MyControllerAdvice#handleMyException

Visit http: // localhost: 8080 / exception to see the behavior

Access with curl. You can see that it is handled by the resolveException method of the MyHandlerExceptionResolver class.

$ curl http://localhost:8080/exception
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div>Error: <span>MyHandlerExceptionResolver#resolveException</span></div>
</div>

</body>
</html>

Spring Boot Server side output.

MyHandlerExceptionResolver#resolveException
class org.springframework.web.method.HandlerMethod
public org.springframework.web.servlet.ModelAndView com.example.my.MyController.handleException(org.springframework.web.servlet.ModelAndView) throws java.lang.Exception

Exception handling flow within Spring Web MVC

The DispatcherServlet class is handling multiple HandlerExceptionResolver objects.

ExceptionHandlerExceptionResolver class, ResponseStatusExceptionResolver class, DefaultHandlerExceptionResolver class are prepared, and these classes handle exceptions with their respective roles.

Among them, the ExceptionHandlerExceptionResolver class is processing to call the method with @ExceptionHandler annotation.

DispatcherServlet (Spring Framework 5.1.9.RELEASE API)

The dispatcher's exception resolution strategy can be specified via a HandlerExceptionResolver, for example mapping certain exceptions to error pages. Default are ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver. These HandlerExceptionResolvers can be overridden through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).

View the source code of the Spring Web MVC DispatcherServlet class.

spring-framework/DispatcherServlet.java at v5.1.9.RELEASE · spring-projects/spring-framework

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    @Nullable Object handler, Exception ex) throws Exception {

  // Success and error responses may use different content types
  request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

  // Check registered HandlerExceptionResolvers...
  ModelAndView exMv = null;
  if (this.handlerExceptionResolvers != null) {
    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
        break;
      }
    }
  }

In the processHandlerException method, get the HandlerExceptionResolver object one by one from this.handlerExceptionResolvers and call the resolveException method. The resolveException method of each object handles the exception and returns a ModelAndView object.

Let's take a look at the object being processed by the IntelliJ IDEA debugger.

dispatcherservlet-1.png dispatcherservlet-2.png

You can see that the HandlerExceptionResolverComposite object manages the ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver.

The ExceptionHandlerExceptionResolver object has an instance variable called exceptionHandlerAdviceCache, which contains an object of the class annotated with @ControllerAdvice.

If you look at the source code of ExceptionHandlerExceptionResolver, you can see that it is registered in exceptionHandlerAdviceCache.

spring-framework/ExceptionHandlerExceptionResolver.java at v5.1.9.RELEASE · spring-projects/spring-framework

private void initExceptionHandlerAdviceCache() {
  if (getApplicationContext() == null) {
    return;
  }

  List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  AnnotationAwareOrderComparator.sort(adviceBeans);

  for (ControllerAdviceBean adviceBean : adviceBeans) {
    Class<?> beanType = adviceBean.getBeanType();
    if (beanType == null) {
      throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
    }
    ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
    if (resolver.hasExceptionMappings()) {
      this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
    }
    if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
      this.responseBodyAdvice.add(adviceBean);
    }
  }

spring-framework/ExceptionHandlerMethodResolver.java at v5.1.9.RELEASE · spring-projects/spring-framework

private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
  ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
  Assert.state(ann != null, "No ExceptionHandler annotation");
  result.addAll(Arrays.asList(ann.value()));
}

Reference material

Recommended Posts

Use @ControllerAdvice, @ExceptionHandler, HandlerExceptionResolver in Spring Boot to catch exceptions
How to use CommandLineRunner in Spring Batch of Spring Boot
How to use Lombok in Spring
How to call and use API in Java (Spring Boot)
How to use ModelMapper (Spring boot)
Use DynamoDB query method in Spring Boot
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to use built-in h2db with spring boot
How to use Spring Boot session attributes (@SessionAttributes)
Use Servlet filter in Spring Boot [Spring Boot 1.x, 2.x compatible]
How to add a classpath in Spring Boot
How to bind to property file in Spring Boot
Use Interceptor in Spring
How to create a Spring Boot project in IntelliJ
Introduce swagger-ui to REST API implemented in Spring Boot
How to use In-Memory Job repository in Spring Batch
Set context-param in Spring Boot
Try Spring Boot from 0 to 100.
How to change application.properties settings at boot time in Spring boot
Spring Boot 2 multi-project in Gradle
How to use the same Mapper class in multiple data sources with Spring Boot + MyBatis
Introduction to Spring Boot ① ~ DI ~
Introduction to Spring Boot ② ~ AOP ~
Spring Boot + Springfox springfox-boot-starter 3.0.0 Use
Use thymeleaf3 with parent without specifying spring-boot-starter-parent in Spring Boot
Use Spring JDBC with Spring Boot
Major changes in Spring Boot 1.5
Introduction to Spring Boot Part 1
How to control transactions in Spring Boot without using @Transactional
[Sprint Boot] How to use 3 types of SqlParameterSource defined in org.springframework.jdbc.core.namedparam
What I did in the migration from Spring Boot 1.4 series to 2.0 series
What I did in the migration from Spring Boot 1.5 series to 2.0 series
Use Basic Authentication with Spring Boot
Spring Boot application development in Eclipse
How to use Spring Data JDBC
Write test code in Spring Boot
How to set Spring Boot + PostgreSQL
How to use InjectorHolder in OpenAM
Implement REST API in Spring Boot
What is @Autowired in Spring boot?
Use DBUnit for Spring Boot test
Upgrade spring boot from 1.5 series to 2.0 series
How to use classes in Java?
Beginning with Spring Boot 0. Use Spring CLI
Catch multiple exceptions together in java
Thymeleaf usage notes in Spring Boot
Spring.messages.fallback-to-system-locale: false is required to default message.properties for i18n support in Spring boot
Procedure to make the value of the property file visible in Spring Boot
My memorandum that I want to make ValidationMessages.properties UTF8 in Spring Boot
How to not start Flyway when running unit tests in Spring Boot
[For internal use] For those assigned to the Spring Boot project (under construction)
Multilingual Locale in Java How to use Locale
[Introduction to Spring Boot] Form validation check
Convert request parameter to Enum in Spring
Build Spring Boot + Docker image in Gradle
Static file access priority in Spring boot
How to include Spring Tool in Eclipse 4.6.3?
Output Spring Boot log in json format
Local file download memorandum in Spring Boot
How to use Docker in VSCode DevContainer
Create Java Spring Boot project in IntelliJ