Mastering Error Handling in Spring Boot with ProblemDetails
Elevate your API’s error responses: Standardize, Inform, and Simplify with RFC 9457
Introduction
Hello everyone! In this article I will try to cover a game — changer for API error handling in Spring Boot — the ProblemDetails RFC. If you spent a long time wrestling with inconsistent error responses or scratching your head over how to structure your API’s error message, you’re in for a treat.
What is the deal with ProblemDetails?
First of all the ProblemDetails is not just another Spring Boot feature — it is an IETF standard (RFC 9457) that defines a “problem detail” as a way to carry machine-readable details error in a HTTP response.
Since Spring Boot 3.0 the ProblemDetails are natively supported, making it easier than ever to implement this standard in your applications. Before diving into implementation details let’s answer the question “Why you should care?”
Better client — side error handling
Improved API documentation
Consistency across your services
Implementation
In this article, I’m using Spring Boot 3.3.4 where ProblemDetails is enabled by default. If you are using an older version of Spring Boot you have to configure ProblemDetails manually.
package me.vrnsky.problemdetails.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@Configuration
public class ProblemDetailsConfig {
@Bean
public ResponseEntityExceptionHandler exceptionHandler() {
return new ResponseEntityExceptionHandler() {
@Override
protected ResponseEntity<Object> handleException(Exception exception,
@Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode,
WebRequest request) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(statusCode, ex.getMessage());
problemDetail.setProperty("timestamp", Instant.now());
problemDetail.setProperty("exception", ex.getClass().getName());
return super.handleExceptionInternal(ex, problemDetail, headers, statusCode, request);
}
}
Let’s now create a basic REST API Controller that will simulate fetching user data from storage.
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
if (id <= 0) {
throw new IllegalArgumentException("User ID must be positive!");
}
return userService.getUserById(id);
}
@Data
public static class User {
private Long id;
private String username;
}
}
Let’s test our API with curl.
curl "http://localhost:8080/users/-1"
After hitting this URL we got following response.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "User ID must be positive",
"instance": "/api/user/-1",
"timestamp": "2024-10-07T10:30:45.123Z",
"exception": "java.lang.IllegalArgumentException"
}
We got a structured, consistent error response without having to manually construct it each time.
Improvements
The code above is agood fit for common and prototyping scenarios, you might want or have to customize it in real — world services. You can:
Elevate custom exception types that map to specific HTTP status codes.
Provide more context to your ProblemDetails, like error codes or links to documentation.
Conclusion
Applying ProblemDetails in your Spring Boot application is a small change that can lead to a big impact on the quality and consistency of your API. The traditional approach has served us well, meanwhile, ProblemDetails offers a more robust, standardized, and future — proof solution. You have to keep in mind that error handling it not only about reporting failures, it is also about providing actionable information that helps solve problems.