[JAVA] Minimal customization of Spring Boot error page (implementation of ErrorController interface)

Overview

--The default settings for Spring Boot return a Whitelabel Error Page or JSON when a 404 Not Found or 500 Internal Server Error occurs. --HTML is returned for access from a web browser --JSON is returned for access from machine clients such as curl --You can't stop JSON from being returned with your own error.html or error / 404.html --It is better to prepare a class that implements the ErrorController interface and customize the HTML and JSON returned when an error occurs. --This operation check environment: Java 11 + Spring Boot 2.2.1

Minimum error page implementation example

When an error occurs, it is displayed as 404 Not Found.

Minimum implementation class for ErrorController interface

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

/**
 *Error controller for the entire web application.
 *Implementation class of the ErrorController interface.
 */
@Controller
@RequestMapping("/error") //Mapping to error page
public class MySimpleErrorController implements ErrorController {

  /**
   *Returns the path of the error page.
   *
   * @return Error page path
   */
  @Override
  public String getErrorPath() {
    return "/error";
  }

  /**
   *Returns a ModelAndView object for the response.
   *
   * @param req request information
   * @param mav response information
   * @ModelAndView object for return HTML response
   */
  @RequestMapping
  public ModelAndView error(HttpServletRequest req, ModelAndView mav) {

    //404 Not Found for any error
    //The stator code and output contents can be customized as needed.
    mav.setStatus(HttpStatus.NOT_FOUND);

    //Specify the view name
    //Thymeleaf template src/main/resources/templates/error.Use html
    mav.setViewName("error");

    return mav;
  }
}

Thymeleaf HTML template file src / main / resources / templates / error.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>

Operation verification

404 Not Found is now returned to the client instead of 500 Internal Server Error etc. when an error occurs. It also now returns HTML instead of JSON for access from machine clients such as curl.

$ curl --include http://localhost:8080/
HTTP/1.1 404 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Transfer-Encoding: chunked
Date: Mon, 18 Nov 2019 13:33:52 GMT

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>

Error page implementation example that outputs detailed error information

Build detailed error information so that you can select what to return to the client and output it to the log.

DefaultErrorAttributes class

Spring Boot's DefaultErrorAttributes class makes it easy to build detailed error information.

DefaultErrorAttributes (Spring Boot Docs 2.2.1.RELEASE API)

Default implementation of ErrorAttributes. Provides the following attributes when possible:

・ Timestamp --The time that the errors were extracted ・ Status --The status code ・ Error --The error reason ・ Exception --The class name of the root exception (if configured) ・ Message --The exception message ・ Errors --Any ObjectErrors from a BindingResult exception ・ Trace --The exception stack trace ・ Path --The URL path when the exception was raised

ErrorController interface implementation class

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 *Error controller for the entire web application.
 *Implementation class of the ErrorController interface.
 */
@Controller
@RequestMapping("/error") //Mapping to error page
public class MyDetailErrorController implements ErrorController {

  /**
   *Returns the path of the error page.
   *
   * @return Error page path
   */
  @Override
  public String getErrorPath() {
    return "/error";
  }

  /**
   *Extract error information.
   *
   * @param req request information
   * @return error information
   */
  private static Map<String, Object> getErrorAttributes(HttpServletRequest req) {
    //Get detailed error information with the DefaultErrorAttributes class
    ServletWebRequest swr = new ServletWebRequest(req);
    DefaultErrorAttributes dea = new DefaultErrorAttributes(true);
    return dea.getErrorAttributes(swr, true);
  }

  /**
   *Determine the HTTP status for the response.
   *
   * @param req request information
   * @HTTP status for return response
   */
  private static HttpStatus getHttpStatus(HttpServletRequest req) {
    //Determine HTTP status
    //Here, all except 404 are set to 500.
    Object statusCode = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
    if (statusCode != null && statusCode.toString().equals("404")) {
      status = HttpStatus.NOT_FOUND;
    }
    return status;
  }

  /**
   *Returns a ModelAndView object for the HTML response.
   *
   * @param req request information
   * @param mav response information
   * @ModelAndView object for return HTML response
   */
  @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  public ModelAndView myErrorHtml(HttpServletRequest req, ModelAndView mav) {

    //Get error information
    Map<String, Object> attr = getErrorAttributes(req);

    //Determine HTTP status
    HttpStatus status = getHttpStatus(req);

    //Set HTTP status
    mav.setStatus(status);

    //Specify the view name
    //Src for Thymeleaf template/main/resources/templates/error.html
    mav.setViewName("error");

    //Set the information you want to output
    mav.addObject("status", status.value());
    mav.addObject("timestamp", attr.get("timestamp"));
    mav.addObject("error", attr.get("error"));
    mav.addObject("exception", attr.get("exception"));
    mav.addObject("message", attr.get("message"));
    mav.addObject("errors", attr.get("errors"));
    mav.addObject("trace", attr.get("trace"));
    mav.addObject("path", attr.get("path"));

    return mav;
  }

  /**
   *Returns a ResponseEntity object for the JSON response.
   *
   * @param req request information
   * @ResponseEntity object for return JSON response
   */
  @RequestMapping
  public ResponseEntity<Map<String, Object>> myErrorJson(HttpServletRequest req) {

    //Get error information
    Map<String, Object> attr = getErrorAttributes(req);

    //Determine HTTP status
    HttpStatus status = getHttpStatus(req);

    //Set the information you want to output
    Map<String, Object> body = new HashMap();
    body.put("status", status.value());
    body.put("timestamp", attr.get("timestamp"));
    body.put("error", attr.get("error"));
    body.put("exception", attr.get("exception"));
    body.put("message", attr.get("message"));
    body.put("errors", attr.get("errors"));
    body.put("trace", attr.get("trace"));
    body.put("path", attr.get("path"));

    //Output information in JSON
    return new ResponseEntity<>(body, status);
  }
}

Thymeleaf HTML template file src / main / resources / templates / error.html

Outputs the value specified in the ErrorController interface implementation class.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>/error</title>
</head>
<body>
<div th:text="'timestamp: ' + ${timestamp}"></div>
<div th:text="'status: ' + ${status}"></div>
<div th:text="'error: ' + ${error}"></div>
<div th:text="'exception: ' + ${exception}"></div>
<div th:text="'message: ' + ${message}"></div>
<div th:text="'errors: ' + ${errors}"></div>
<div th:text="'trace: ' + ${trace}"></div>
<div th:text="'path: ' + ${path}"></div>
</body>
</html>

Operation verification

An example of returning a 500 Internal Server Error in HTML. Detailed error information is embedded in the HTML.

$ curl --include -H "accept: text/html" http://localhost:8080/sample
HTTP/1.1 500 
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Transfer-Encoding: chunked
Date: Mon, 18 Nov 2019 13:55:21 GMT
Connection: close

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>/error</title>
</head>
<body>
<div>timestamp: Mon Nov 18 22:55:21 JST 2019</div>
<div>status: 500</div>
<div>error: Internal Server Error</div>
<div>exception: java.lang.RuntimeException</div>
<div>message: This is a sample exception.</div>
<div>errors: null</div>
<div>trace: java.lang.RuntimeException: This is a sample exception.
	at com.example.demo.DemoApplication.index(DemoApplication.java:18)
(The following is omitted)

An example of returning a 500 Internal Server Error in JSON. Detailed error information is included in the JSON.

$ curl --include http://localhost:8080/sample
HTTP/1.1 500 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 18 Nov 2019 13:55:31 GMT
Connection: close

{"exception":"java.lang.RuntimeException","path":"/sample","trace":"java.lang.RuntimeException: This is a sample exception.\n\tat com.example.demo.DemoApplication.index(DemoApplication.java:18)\n\t(Omission)java.base/java.lang.Thread.run(Thread.java:834)\n","error":"Internal Server Error","message":"This is a sample exception.","errors":null,"status":500,"timestamp":"2019-11-18T13:55:31.644+0000"}

Reference material

-Spring Boot Whitelabel Error Page and JSON Response -Qiita -Customize the display when an error such as 404 Not Found occurs in Spring Boot -Qiita

Recommended Posts

Minimal customization of Spring Boot error page (implementation of ErrorController interface)
Spring Boot Whitelabel Error Page and JSON Response
[FCM] Implementation of message transmission using FCM + Spring boot
[Java / Spring Boot] Spring security ④ --Implementation of login process
[Java / Spring Boot] Spring security ⑤ --Implementation of logout processing
I want to control the default error message of Spring Boot
Unknown error in line 1 of pom.xml when using Spring Boot in Eclipse
Memorandum of understanding when Spring Boot 1.5.10 → Spring Boot 2.0.0
Spring Boot + PostgreSQL error resolution method
Going out of message (Spring boot)
[Spring Boot] Role of each class
Error in implementation when implementing Spring validation
WebMvcConfigurer Memorandum of Understanding for Spring Boot 2.0 (Spring 5)
Javaw.exe error when starting Spring Boot (STS)
Starting with Spring Boot 2.3, the default error page no longer contains detailed error information