“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.