Improve Spring Boot REST API error handling

introduction

Spring Boot provides an excellent exception handling mechanism. The default implementation of ErrorController is good at catching and handling exceptions. In addition, you can also implement @ExceptionHandler yourself to catch and handle specific exceptions. However, there is room for improvement here:

Even if a custom @ExceptionHandler is used to implement some exceptions, it will still slip through the net, and the ErrorController will handle it. The @ExceptionHandler vs ErrorController scheme could be improved.

Default errors can sometimes look confusing:

{
  "timestamp": "2018-09-23T15:05:32.681+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "NotBlank.dto.name",
        "NotBlank.name",
        "NotBlank.java.lang.String",
        "NotBlank"
      ],
      "arguments": [
        {
          "codes": [
            "dto.name",
            "name"
          ],
          "arguments": null,
          "defaultMessage": "name",
          "code": "name"
        }
      ],
      "defaultMessage": "{name.not_blank}",
      "objectName": "dto",
      "field": "name",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotBlank"
    }
  ],
  "message": "Validation failed for object='dto'. Error count: 1",
  "path": "/"
}

Of course, we could do this by registering custom ErrorAttributes, but it would be nice if the default error implementation could be improved.

For errors generated by validation, some parameters in the constraints can be opened to the information display. For example, you can use the {} placeholder to pass the minimum value of int to the message display. But this method does not apply to other exception handling:

public class UserAlreadyExistsException extends RuntimeException {

    // How can I expose this value to the interpolated message?
    private final String username;

    // constructors and getters and setters
}		

It would of course be great if all exceptions supported application-level built-in error codes. Sometimes, just the HTTP status code doesn't pinpoint the problem at all. For example, different errors occur in the same EndPoint, and the same status code is reported. How to analyze?

 

improve proposals

errors-spring-boot-starter provides unified handling for various exceptions. errors-spring-boot-starter provides on the basis of Spring Boot exception handling mechanism:

 

· Consistent implementation of all exception handling: no problem with validation or binding errors, domain errors, or even Spring-related errors. All exceptions are handled by WebErrorHandler implementation (ErrorController and @ExceptionHandler are no longer required)

· Built-in support for application-level error codes.

· Simple message insertion using MessageSource.

· Supports custom display of HTTP error codes.

· Allow exception parameters to be exposed for use by error messages.

 

Default error display

The default format of the error is JSON, and the schema is defined as follows:  

// The errors array defines the code/messsge combination for each error
{
  "errors": [
    {
      "code": "first_error_code",
      "message": "1st error message"
    }
  ]
}

 

To customize the display, just register the HttpErrorAttributesAdapter to implement a Spring Bean.

 

Unified error handling

All exceptions are handled by WebErrorHandler. Starter will query the built-in WebErrorHandler by default to handle the following exceptions:

· All validation or binding exceptions.

· All custom exceptions annotated with @ExceptionMapping.

· Spring MVC exception.

· Spring Security will also process if it is on the classpath.

 

It is also possible to implement WebErrorHandler to register a custom exception handler Spring Bean.

 

Built-in error code support

Although HTTP status codes are recommended for RESTful API s, sometimes we need more information to locate the problem. In this case, you need to use the error code. Think of error codes as more readable error description machine code. Each exception can be mapped to at least one error code.

The exception-to-error-code differs depending on the type of exception:

· The error code generated by the verification is extracted from the message attribute in the annotation, such as @NotBlank(message = "name.required").

The errorCode property in the @ExceptionMapping annotation looks like this:

@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {}

 

· The following is a custom implementation of WebErrorHandler:

public class ExistedUserHandler implements WebErrorHandler {

    @Override
    public boolean canHandle(Throwable exception) {
        return exception instanceof UserAlreadyExistsException;
    }

    @Override
    public HandledException handle(Throwable exception) {
        return new HandledException("user.already_exists", BAD_REQUEST, null);
    }
}

 

Expose parameters

As with Spring Boot, you can also pass validation parameters via annotations, such as @Min(value = 18, message = "age.min") to interpolate the parameters into the displayed message:

age.min = The minimum age is {0}!

Additionally, custom exceptions can be annotated with @ExposeAsArg. For example, if username is reported as occupied in the following message:

user.already_exists=Another user with the '{0}' username already exists

Implementation code:

@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {
    @ExposeAsArg(0) private final String username;

    // constructor
}

 

 

Tags: Java Big Data Spring Spring Boot Back-end

Posted by MattWeet on Fri, 20 May 2022 18:31:37 +0300