A Spring Boot 4 starter that extends RFC 9457 ProblemDetail responses with
field-level validation error details for both Spring WebMVC and Spring WebFlux.
Choose the starter that matches your web stack and Spring Boot line.
| Spring Boot line | Branch | Artifact prefix | Version line |
|---|---|---|---|
3.x |
3.x |
extended-problem-detail-boot3-* |
Use the latest 3.x release |
4.x |
main / 4.x |
extended-problem-detail-boot4-* |
Use the latest 4.x release |
<dependency>
<groupId>io.github.sbracely</groupId>
<artifactId>extended-problem-detail-boot4-webmvc-spring-boot-starter</artifactId>
<version>4.0.2</version>
</dependency><dependency>
<groupId>io.github.sbracely</groupId>
<artifactId>extended-problem-detail-boot4-webflux-spring-boot-starter</artifactId>
<version>4.0.2</version>
</dependency>No additional configuration is required. The exception handler registers automatically.
| Scope | Version | Notes |
|---|---|---|
| Spring Boot 4 line | 4.0.x |
Use extended-problem-detail-boot4-* artifacts |
| Minimum Java | 17+ |
Project source and public API target Java 17 or newer |
| Verified in this repository | Spring Boot 4.0.6 / Java 25.0.2 |
Current reactor test run covers the Boot 4 line |
Both example applications expose OpenAPI documents at runtime:
- WebMVC example:
/v3/api-docs,/v3/api-docs.yaml, and/swagger-ui/index.html - WebFlux example:
/v3/api-docs,/v3/api-docs.yaml, and/swagger-ui/index.html
The runtime OpenAPI documents focus on the application/problem+json exception payload and its
errors / Error structure. For concrete request parameters and ways to trigger each
error response, refer to the example controller tests in each module.
When the example applications are running, /swagger-ui/index.html provides interactive "Try it out"
requests against the live example endpoints.
Each example module also provides a Maven profile that generates offline OpenAPI spec files
without requiring you to start the application manually. The profile is enabled by default, so
running the example module test lifecycle also refreshes the exported files in docs.
Generate WebMVC offline docs:
./mvnw -pl webmvc/example -Poffline-openapi-docs verify.\mvnw.cmd -pl webmvc\example -Poffline-openapi-docs verifyGenerate WebFlux offline docs:
./mvnw -pl webflux/example -Poffline-openapi-docs verify.\mvnw.cmd -pl webflux\example -Poffline-openapi-docs verifyGenerated files are written to docs inside the example module:
openapi.jsonopenapi.yaml- WebMVC exception response table
- WebFlux exception response table
These exported OpenAPI files make it easier to inspect which exceptions map to which
application/problem+json response bodies without manually starting the example service first.
When a validation exception occurs, the response extends the standard RFC 9457 body with an errors array:
{
"title": "Bad Request",
"status": 400,
"detail": "Invalid request content.",
"instance": "/api/users",
"errors": [
{
"type": "REQUEST_BODY",
"target": "email",
"message": "must be a well-formed email address"
},
{
"type": "REQUEST_BODY",
"target": "password",
"message": "size must be between 8 and 20"
}
]
}On Spring Framework 7 / Spring Boot 4, ProblemDetail.type is not set by default, so the serialized
JSON usually omits the type field unless your application sets it explicitly.
| Field | Description |
|---|---|
type |
Error source: QUERY_PARAMETER, PATH_VARIABLE, REQUEST_BODY, MODEL_ATTRIBUTE, REQUEST_PART, MATRIX_VARIABLE, COOKIE, HEADER, or BUSINESS |
target |
Field name, parameter name, or other identifier of the offending input |
message |
Human-readable description of the constraint violation |
| Exception | Trigger |
|---|---|
MethodArgumentNotValidException |
@Valid on a @RequestBody or @ModelAttribute |
HandlerMethodValidationException |
@Validated on controller method parameters |
WebExchangeBindException |
Binding failure on a @ModelAttribute |
MethodValidationException |
Bean-level method validation via @Validated |
extended:
problem-detail:
enabled: true # Set to false to disable the auto-configured handler (default: true)
errors-property-name: errors # ProblemDetail properties entry name for structured errors (default: errors)
controller-advice-order: 0 # @ControllerAdvice order for built-in handlers (default: 0)
logging:
at-level: INFO # Level used to log caught exceptions: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF (default: INFO)
print-stack-trace: false # Include full stack trace in the log entry (default: false)Equivalent application.properties:
extended.problem-detail.enabled=true
extended.problem-detail.errors-property-name=errors
extended.problem-detail.controller-advice-order=0
extended.problem-detail.logging.at-level=INFO
extended.problem-detail.logging.print-stack-trace=falseIf extended.problem-detail.enabled is omitted, the starter logs a one-time INFO message at startup
to make the default explicit and show how to disable it. Set the property explicitly to true or
false to suppress that reminder.
For business-level errors, extend ErrorResponseException and populate the ProblemDetail body directly:
public class OrderNotFoundException extends ErrorResponseException {
public OrderNotFoundException(String orderId) {
super(HttpStatus.NOT_FOUND,
ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "Order not found: " + orderId),
null);
}
}To include structured errors in the response, add them to the ProblemDetail properties using the
configured entry name (extended.problem-detail.errors-property-name, default errors):
public class OrderNotFoundException extends ErrorResponseException {
public OrderNotFoundException(String orderId, String errorsPropertyName) {
super(HttpStatus.NOT_FOUND, createBody(orderId, errorsPropertyName), null);
}
private static ProblemDetail createBody(String orderId, String errorsPropertyName) {
ProblemDetail body =
ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "Order not found: " + orderId);
body.setProperty(errorsPropertyName, List.of(
new Error(Error.Type.BUSINESS, "orderId", "Order not found: " + orderId)));
return body;
}
}The auto-configured handler is registered as a @ConditionalOnMissingBean. Declaring your own subclass as a
Spring bean replaces it entirely.
Extend MvcExtendedProblemDetailExceptionHandler (WebMVC) or FluxExtendedProblemDetailExceptionHandler (WebFlux).
The base class exposes logger and extendedProblemDetailLog for use in overriding methods.
Override a handler already covered by the base class:
@RestControllerAdvice
public class CustomExceptionHandler extends MvcExtendedProblemDetailExceptionHandler {
public CustomExceptionHandler(ExtendedProblemDetailLog extendedProblemDetailLog) {
super(extendedProblemDetailLog);
}
// Override to customise the response for 405 Method Not Allowed
@Override
protected @Nullable ResponseEntity<Object> handleHttpRequestMethodNotSupported(
HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
extendedProblemDetailLog.log(logger, ex, "handleHttpRequestMethodNotSupported");
return super.handleHttpRequestMethodNotSupported(ex, headers, status, request);
}
}Add a handler for an exception type not covered by the base class:
@RestControllerAdvice
public class CustomExceptionHandler extends MvcExtendedProblemDetailExceptionHandler {
public CustomExceptionHandler(ExtendedProblemDetailLog extendedProblemDetailLog) {
super(extendedProblemDetailLog);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDenied(AccessDeniedException ex, WebRequest request) {
extendedProblemDetailLog.log(logger, ex, "handleAccessDenied");
ProblemDetail body = createProblemDetail(ex, HttpStatus.FORBIDDEN,
"Access denied", null, null, request);
return handleExceptionInternal(ex, body, new HttpHeaders(), HttpStatus.FORBIDDEN, request);
}
}HandlerMethodValidationException is resolved via the Visitor pattern. Each parameter annotation type has a
dedicated resolveXxx method in ExtendedProblemDetailErrorResolver that you can override to change how errors
are built.
Example — override resolveCookieValue to use the annotation's name attribute as the error target:
@RestControllerAdvice
public class CustomExceptionHandler extends MvcExtendedProblemDetailExceptionHandler {
public CustomExceptionHandler(ExtendedProblemDetailLog extendedProblemDetailLog) {
super(extendedProblemDetailLog);
}
@Override
protected void resolveCookieValue(HandlerMethodValidationException ex,
CookieValue cookieValue,
ParameterValidationResult result,
List<Error> errorList) {
String target = cookieValue.name().isEmpty()
? result.getMethodParameter().getParameterName()
: cookieValue.name();
addParameterErrors(result, Error.Type.COOKIE, target, errorList);
}
}All available override points:
| Method | Parameter annotation |
|---|---|
resolveCookieValue |
@CookieValue |
resolveMatrixVariable |
@MatrixVariable |
resolveModelAttribute |
@ModelAttribute |
resolvePathVariable |
@PathVariable |
resolveRequestBody |
@RequestBody (object) |
resolveRequestBodyValidationResult |
@RequestBody (scalar) |
resolveRequestHeader |
@RequestHeader |
resolveRequestParam |
@RequestParam |
resolveRequestPart |
@RequestPart |
resolveOther |
other / unrecognized types |
| Artifact | Description |
|---|---|
extended-problem-detail-boot4-common |
Shared Boot 4 support layer |
extended-problem-detail-boot4-webmvc-autoconfigure |
Boot 4 WebMVC auto-configuration |
extended-problem-detail-boot4-webmvc-spring-boot-starter |
Boot 4 WebMVC starter |
extended-problem-detail-boot4-webflux-autoconfigure |
Boot 4 WebFlux auto-configuration |
extended-problem-detail-boot4-webflux-spring-boot-starter |
Boot 4 WebFlux starter |
The root, webmvc, and webflux aggregator POMs are reactor-only and are not published to Maven Central.