Error Details in Spring Boot with RFC 7807

“A man would do nothing if he waited until he could do it so well that no one could find fault.”

John Henry Newman

Spring Boot has an excellent error mechanism for REST endpoints. A @RestControllerAdvice provides handling methods for various types of exceptions that can occur when processing REST requests.

In the following example, the RestExceptionHandler class provides a method for the NoSuchElementException. Instead of just one method for an exception, various other methods can of course be listed here.

@RestControllerAdvice
public class RestExceptionHandler {

  public record ErrorMessage(int status, String message, String description) {
        
    public ErrorMessage(HttpStatus status, String message, String description) {
      this(status.value(), message, description);
    }
  }

  @ExceptionHandler(NoSuchElementException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public ErrorMessage noSuchElement(NoSuchElementException e, WebRequest request) {
    log.warn("no such element {}", e.getMessage(), e); 
    return new ErrorMessage(HttpStatus.NOT_FOUND, e.getMessage(), request.getDescription(false));
  }
}

The noSuchElement method generates an error response of the ErrorMessage type with the HTTP status code 404 and the explaining status, message and description fields.

HTTP/1.1 404 
Content-Type: application/problem

{
  "status": 404,
  "message": "No value present",
  "description": "uri=/anchestors/111111"
}

As long as only one microservice or only one development team is involved, this is a good approach. It becomes problematic when various microservices come up with different error formats. Different field identifiers, missing fields or completely different responses can make collaboration between microservices and teams more difficult.

Fortunately, some experts have already addressed this problem. RFC 7807, titled “Problem Details for HTTP APIs” is a specification that defines a standard way to return machine-readable error information in HTTP responses. It was published by the Internet Engineering Task Force (IETF) in March 2016.

The main goal of RFC 7807 is to provide a consistent and easy-to-understand structure for error messages returned by HTTP APIs, so that developers can handle errors more effectively in their applications. The specification outlines a simple JSON object structure, called a “problem details” object, that includes fields such as “type,” “title,” “status,” “detail,” and “instance.”

The “type” field is a URI that identifies the type of problem, which can help clients understand the problem without requiring additional human-readable information. The “title” field provides a short, human-readable summary of the problem type. The “status” field reflects the HTTP status code associated with the problem. The “detail” field gives a more detailed explanation of the problem for the client. The “instance” field is a URI that identifies the specific occurrence of the problem.

By standardizing the format of error responses, RFC 7807 aims to make it easier for clients to process and display error information to users in a way that is more understandable and actionable.

So how does RFC 7807 help us specifically with Spring Boot applications? The answer is simple and was to be expected in the Spring Boot environment. Spring Boot already supports RFC 7807 through the ErrorResponse class. It only needs to be filled with the necessary data in the @RestControllerAdvice and specified as the return value of the noSuchElement method.

@RestControllerAdvice
public class RestExceptionHandler {

  @ExceptionHandler(NoSuchElementException.class)
  public ErrorResponse noSuchElement(NoSuchElementException e) {
    log.warn("no such element {}", e.getMessage(), e); 
    return ErrorResponse.create(e, HttpStatus.NOT_FOUND, e.getMessage());
  }
}

If you compare the two implementations, you will immediately notice that this method has no @ResponseStatus annotation. Spring Boot knows that the ErrorResponse contains an HttpStatus and uses this for the HTTP status code of the response.

HTTP/1.1 404 
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "No value present",
  "instance": "/anchestors/111111"
}

The content type of the response is not application/json but application/problem+json. This content type is used to clearly distinguish error details from other responses in accordance with RFC 7807.

The type and instance fields are filled automatically. The about:blank URI, when used as a problem type, indicates that the problem has no additional semantics beyond that of the HTTP status code.

However, slightly more complex ErrorResponse instances can also be created with the help of a builder. In the following example, a user’s request violates the EU GDPR. In this case, HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS (451) is specified as the status, the URI https://schegge.de/problems/gdpr as the type and a specific title.

@RestControllerAdvice
public class RestExceptionHandler {

  @ExceptionHandler(NoSuchElementException.class)
  public ErrorResponse noSuchElement(NoSuchElementException e) {
    log.warn("no such element {}", e.getMessage(), e); 
    return ErrorResponse.create(e, HttpStatus.NOT_FOUND, e.getMessage());
  }

  @ExceptionHandler(DataProtectionAccessException.class)
  public ErrorResponse legalReason(DataProtectionAccessException e) {
    return ErrorResponse.builder(e, HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS, e.getMessage())
      .type(URI.create("https://schegge.de/problems/gdpr"))
      .title("Zugriff auf diese Daten ist nach der DSGVO nicht gestattet").build();
  }
}

If the legalReason method is called, the response contains the explicitly specified values for type and title.

HTTP/1.1 451
Content-Type: application/problem+json

{
  "type": "https://schegge.de/problems/gdpr",
  "title": "Zugriff auf diese Daten ist nach der DSGVO nicht gestattet",
  "status": 451,
  "detail":"Unavailable For Legal Reasons",
  "instance":"/users/111"
}

In order to fully comply with RFC 7807, a Web URL https://schegge.de/problems/gdpr can also be provided with a detailed text for the problem type.

Of course, even more is possible with the ErrorResponse class. Additional header parameters can be specified and the MessageSource mechanism can be used for the texts. Anyone who wants to customise their own error messages now will certainly discover a lot more.

Schreibe einen Kommentar