I created the error-handling-spring-boot-starter library for Spring Boot because I was unhappy with the default response that Spring Boot 2 provides out-of-the-box.
With Spring Boot 3, there is now support for ProblemDetail and we can have a look on how that changes things.
Spring 6 ProblemDetail
If you first want to learn more about using ProblemDetail, I would suggest to first read Spring Boot 3 : Error Responses using Problem Details for HTTP APIs by sivalabs. If you like video more, have a look to Spring 6 and Problem Details on YouTube.
In summary, this is how ProblemDetail works:
-
You need to opt-in to enable it for framework errors by setting
spring.webmvc.problemdetails.enabled=true(orspring.webflux.problemdetails.enabledif you use WebFlux). -
You can also enable it by creating an exception handler that extends from
ResponseEntityExceptionHandler@RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { } -
For your own exceptions, you need to write a custom
ExceptionHandlerthat converts the exception to an instance ofProblemDetail. For example:@RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(BookmarkNotFoundException.class) ProblemDetail handleBookmarkNotFoundException(BookmarkNotFoundException e) { ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage()); problemDetail.setTitle("Bookmark Not Found"); problemDetail.setType(URI.create("https://api.bookmarks.com/errors/not-found")); return problemDetail; } }By doing this, you get a response like this:
{ "type": "https://api.bookmarks.com/errors/not-found", "title": "Bookmark Not Found", "status": 404, "detail": "Bookmark with id: 111 not found", "instance": "/api/bookmarks/111" } -
If you don’t want to write an
ExceptionHandler, you can have your exception classes extend fromorg.springframework.web.ErrorResponseExceptionwhich forces you to create aProblemDetailinstance in the constructor of your custom exception. Check out the linked blog post for example. -
You can add custom fields in the error response by using the
propertiesMap ofProblemDetail.
The error-handling-spring-boot-starter library
As a reminder, this is how a typical error response looks like with this library:
{
"code": "USER_NOT_FOUND",
"message": "Could not find user with id UserId{id=8c7fb13c-0924-47d4-821a-36f73558c898}",
"userId": "8c7fb13c-0924-47d4-821a-36f73558c898"
}
The only thing you need to have this, is an exception like this:
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseErrorCode("USER_NOT_FOUND")
public class UserNotFoundException extends RuntimeException {
private final UserId userId;
public UserNotFoundException(UserId userId) {
super(String.format("Could not find user with id %s", userId));
this.userId = userId;
}
@ResponseErrorProperty
public String getUserId() {
return userId.getValue();
}
}
See Better Error Handling for Your Spring Boot REST APIs for a more detailed introduction to the library.
When we compare how error-handling-spring-boot-starter works to using ProblemDetail, we can notice the following:
-
With error-handling-spring-boot-starter, you can just add the dependency on your classpath. It becomes enabled right away.
-
It works for any
Exception, there is no need to extend from the Spring framework exception typeErrorResponseExceptionand to manually write code to create aProblemDetailinside each of those exceptions. -
By annotating your custom exception, you can add fields to the response. For example, just annotate a getter on your custom exception with
@ResponseErrorPropertyand the response of that getter will be automatically added to the error response. -
The error-handling-spring-boot-starter has a rich configuration model to be able to influence the
codeand themessagethat is used for exceptions. Both those from your own code and exceptions from other libraries. All by just adding some properties to yourapplication.properties. -
There is no need to write a custom exception handler. Everything is driven from the exceptions you throw.
-
ProblemDetail is an implementation of RFC 7807. The response used by error-handling-spring-boot-starter is not backed by any standard. One of the things this standard dictates is:
Consumers MUST use the "type" string as the primary identifier for the problem type.With
typea field that should contain an URI reference.I find the use of the
codefield in responses oferror-handling-spring-boot-startera bit easier to work with. They are also automatically derived from the name of the Exception.I really wonder how much projects will actually put a valid URI in there. I also wonder if people will use different URI’s depending on if the application is running in production or not for example.
Given all of the above, I think that error-handling-spring-boot-starter still provides a lot of value, also for Spring Boot 3 and Spring 6.
To use it with Spring Boot 3, be sure to use version 4.0.0.
Conclusion
This post showed an overview of the error-handling-spring-boot-starter library and how it compares to ProblemDetail in Spring 6.
If you have any questions or remarks, feel free to post a comment at GitHub discussions.