Mastering RFC 7807 Problem Details in Spring Boot 3: A Complete Guide
Learn how to implement RFC 7807 Problem Details in Spring Boot 3, covering the JSON format, Spring’s built‑in support, configuration steps, custom extensions, and practical code examples for handling errors with ProblemDetail, ErrorResponse, and ResponseEntityExceptionHandler.
Overview
RFC 7807 defines a standard JSON and XML format for conveying detailed error information in HTTP responses, allowing API clients to understand both high‑level error classes (via status codes) and fine‑grained problem specifics.
Problem Detail Message Format
A Problem Detail is a JSON object serialized with the media type application/problem+json. Example responses:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://pack.com/probs/out-of-credit",
"title": "Insufficient credit.",
"detail": "Your balance is 30 but you need 50.",
"instance": "/account/12345/msgs/abc",
"balance": 30,
"accounts": ["/account/12345", "/account/67890"]
}Fields include:
type : URI identifying the problem type.
title : Short, human‑readable summary.
detail : Detailed description, optionally with extensions.
instance : URI reference to the specific occurrence.
balance : Example custom field.
accounts : Example custom field providing related links.
Multiple problem extensions can be added, e.g. validation errors:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://example.net/validation-error",
"title": "Your request parameters didn't validate.",
"invalid-params": [
{"name": "age", "reason": "must be a positive integer"},
{"name": "color", "reason": "must be 'green', 'red' or 'blue'"}
]
}Spring Support
Since Spring 6, the framework provides built‑in support for RFC 7807 via the following abstractions:
ProblemDetail : Representation of the RFC 7807 fields plus a map for non‑standard properties.
ErrorResponse : Wraps HTTP status, headers, and a ProblemDetail body.
ErrorResponseException : Base exception that produces an ErrorResponse.
ResponseEntityExceptionHandler (used with @ControllerAdvice): Handles all Spring MVC exceptions and renders them as RFC 7807 responses.
Enable support in application.yml:
spring:
mvc:
problemdetails:
enabled: trueControllers can return ProblemDetail or ErrorResponse directly from @ExceptionHandler or @RequestMapping methods.
Non‑standard Fields
Custom fields can be added via the properties map in ProblemDetail. Spring registers ProblemDetailJacksonMixin so that unknown JSON properties are deserialized into this map and serialized as top‑level fields.
Alternatively, subclass ProblemDetail and add dedicated fields; the copy‑constructor makes it easy to create such subclasses in a @ControllerAdvice implementation.
ProblemDetail Class
public class ProblemDetail {
private static final URI BLANK_TYPE = URI.create("about:blank");
private URI type = BLANK_TYPE;
@Nullable private String title;
private int status;
@Nullable private String detail;
@Nullable private URI instance;
@Nullable private Map<String, Object> properties;
}Test Interface
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("")
public Object index(Integer age) {
System.out.println(1 / 0);
return "success";
}
}Example 1: Basic Usage
@RestControllerAdvice
public class GlobalExceptionHandler {
// Return ProblemDetail directly when any exception occurs
@ExceptionHandler({Exception.class})
public ProblemDetail handle(Exception e) {
ProblemDetail detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), e.getMessage());
return detail;
}
}Result:
Example 2: Adding Extension Properties
@RestControllerAdvice
public class GlobalExceptionHandler {
// Use ErrorResponse to add custom fields
@ExceptionHandler({ArithmeticException.class})
public ErrorResponse handle(Exception e) {
ErrorResponse errorResponse = new ErrorResponseException(HttpStatusCode.valueOf(500), e);
errorResponse.getBody().setProperty("operator_time", new Date());
return errorResponse;
}
}Result:
Example 3: Extending ResponseEntityExceptionHandler
@ControllerAdvice
final class ProblemDetailsExceptionHandler extends ResponseEntityExceptionHandler {
// Custom @ExceptionHandler methods can be added here
}The base ResponseEntityExceptionHandler already defines handlers for many common Spring MVC exceptions.
public abstract class ResponseEntityExceptionHandler implements MessageSourceAware {
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
// ... other exceptions ...
ErrorResponseException.class,
BindException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
// Simplified handling logic
if (ex instanceof HttpMessageNotWritableException theEx) {
return handleHttpMessageNotWritable(theEx, headers, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
// ...
}
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ProblemDetail body = createProblemDetail(ex, status, "Failed to write request", null, null, request);
return handleExceptionInternal(ex, body, headers, status, request);
}
}Processing Principle
When a controller returns a ProblemDetail or ErrorResponse, HttpEntityMethodProcessor detects the return type and writes the body using the appropriate message converter.
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> type = returnType.getParameterType();
return (HttpEntity.class.isAssignableFrom(type) && !RequestEntity.class.isAssignableFrom(type))
|| ErrorResponse.class.isAssignableFrom(type)
|| ProblemDetail.class.isAssignableFrom(type);
}
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HttpEntity<?> httpEntity;
if (returnValue instanceof ErrorResponse response) {
httpEntity = new ResponseEntity<>(response.getBody(), response.getHeaders(), response.getStatusCode());
} else if (returnValue instanceof ProblemDetail detail) {
httpEntity = ResponseEntity.of(detail).build();
}
// Ensure instance URI is set if missing
if (httpEntity.getBody() instanceof ProblemDetail detail && detail.getInstance() == null) {
URI path = URI.create(((ServletServerHttpRequest) inputMessage).getServletRequest().getRequestURI());
detail.setInstance(path);
}
writeWithMessageConverters(httpEntity.getBody(), returnType, inputMessage, outputMessage);
outputMessage.flush();
}
}These mechanisms complete the integration of RFC 7807 Problem Details into Spring applications.
End of article.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
