Designing clear and consistent error responses in a REST API is often harder than it looks. Without a shared standard, each application ends up inventing its own ad-hoc format, which quickly leads to inconsistency and confusion. RFC 7807 - Problem Details for HTTP APIs solves this by defining a simple, extensible JSON structure for error messages.
Problem4J brings this specification into the Spring ecosystem, offering a practical way to model, throw, and handle
API errors using Problem objects. It helps you enforce a consistent error contract across your services, while staying
flexible enough for custom exceptions and business-specific details.
Even though Spring provides ProblemDetail and ErrorResponseException for RFC 7807-compliant error
responses, they have different approach than this library offers. It resolves around throwing ErrorResponseException
(or any exception that extends from it) or returning ProblemDetail in @ExceptionHandler methods for handlers of
individual exceptions. Spring Boot includes some default exception handlers in ResponseEntityExceptionHandler, but
that exceptions usually return exact getMessage() in detail field which may leak framework-internals to client
applications.
In contrast, Problem4J was created to:
- Provide a fully immutable, fluent
Problemmodel with support for extensions. - Support declarative exception mapping via
@ProblemMappingor programmatic one viaProblemException(as a base class) andProblemResolver(as a library-specific way to addException-to-Problemtransformations). - Interpolate exception fields and context metadata (e.g.,
context.traceId) if using declarative approach. - Offer consistent error responses across WebMVC and WebFlux, including validation and framework exceptions.
- Allow custom extensions without boilerplate, making structured errors easier to trace and consume.
- Configure painlessly thanks to Spring Boot autoconfiguration.
- Provide a predefined set of
@RestControllerAdviceimplementations to override default Spring Boot responses, so framework details (such as full exception messages) are not leaked to client applications. - Include support for built-in
ErrorResponseExceptionfor compatibility. - Integrate seamlessly with existing Spring Boot applications, by possibility to enable selected components only (via
@ConditionalOnMissingBeanor application properties).
Problem4J is designed for robust, traceable, and fully configurable REST API errors.
Extensive usage manual explaining library features can be found on repository wiki pages.
The primary ways to produce a Problem response are:
- Throwing a
ProblemExceptionwith a manually builtProblem. - Annotating an exception class with
@ProblemMapping. - Implementing a custom
ProblemResolver.
throw new ProblemException(
Problem.builder()
.type("errors/invalid-request")
.title("Invalid Request")
.status(400)
.detail("not a valid json")
.build());It would produce following response with application/problem+json.
{
"type": "errors/invalid-request",
"title": "Invalid Request",
"status": 400,
"detail": "not a valid json"
}@ProblemMapping(
type = "errors/invalid-request",
title = "Invalid Request",
status = 400,
detail = "{message}: {fieldName}",
extensions = {"userId", "fieldName"})
public class ExampleException extends RuntimeException {
private final String userId;
private final String fieldName;
public ExampleException(String userId, String fieldName) {
super("bad input for user " + userId);
this.userId = userId;
this.fieldName = fieldName;
}
}It would produce following response with application/problem+json.
{
"type": "errors/invalid-request",
"title": "Invalid Request",
"status": 400,
"detail": "bad input for user u-123: age",
"userId": "u-123",
"fieldName": "age"
}@Component
public class ExampleExceptionResolver implements ProblemResolver {
@Override
public Class<? extends Exception> getExceptionClass() {
return ExampleException.class;
}
@Override
public ProblemBuilder resolveBuilder(
ProblemContext context, Exception ex, HttpHeaders headers, HttpStatusCode status) {
return Problem.builder()
.type("errors/invalid-request")
.title("Invalid Request")
.status(400)
.detail("bad input for user " + ((ExampleException) ex).getUserId())
.extension("userId", ((ExampleException) ex).getUserId());
}
}It would produce following response with application/problem+json.
{
"type": "errors/invalid-request",
"title": "Invalid Request",
"status": 400,
"detail": "bad input for user u-456",
"userId": "u-456"
}Add library as dependency to Maven or Gradle. See the actual versions on Maven Central. Add it along with repository in your dependency manager. Java 17 or higher is required to use this library.
It's assumed that Spring Boot will be already present in your project dependencies, as problem4j-spring is only an
extension to it and does not bring it transitively.
- Maven:
<dependencies> <!-- pick the one for your project --> <dependency> <groupId>io.github.malczuuu.problem4j</groupId> <artifactId>problem4j-spring-webflux</artifactId> <version>${version}</version> </dependency> <dependency> <groupId>io.github.malczuuu.problem4j</groupId> <artifactId>problem4j-spring-webmvc</artifactId> <version>${version}</version> </dependency> </dependencies>
- Gradle (Kotlin DSL):
dependencies { // pick the one for your project implementation("io.github.malczuuu.problem4j:problem4j-spring-webflux:${version}") implementation("io.github.malczuuu.problem4j:problem4j-spring-webmvc:${version}") }
This repository maintains two major versions, supporting Spring Boot 3 and 4. The goal is to maintain both versions at least until Spring Boot 3 reaches its end of life or becomes irrelevant.
| branch | info | latest |
|---|---|---|
main |
version 2.x supporting Spring Boot 4.x |
|
release-v1.*.x |
version 1.x supporting Spring Boot 3.x |
problem4j-core- Core library definingProblemmodel andProblemException.problem4j-jackson- Jackson module for serializing and deserializingProblemobjects.problem4j-spring- Spring modules extendingResponseEntityExceptionHandlerfor handling exceptions and returningProblemresponses.
Expand...
Gradle 9.x+ requires Java 17+ to run, but higher Java versions can also be used. All modules of this project are compiled using a Java 17 toolchain, so the produced artifacts are compatible with Java 17, regardless of the Java version Gradle runs on.
./gradlew clean buildTo format the code according to the style defined in build.gradle.kts rules use spotlessApply
task.
./gradlew spotlessApplyTo publish the built artifacts to local Maven repository, run following command, replacing XXXX with the desired
version. By default, the version is derived from git commit hash.
./gradlew -Pversion=XXXX clean build publishToMavenLocal