This library provides a minimal, framework-agnostic Java model of the RFC 7807 "Problem Details" object, with
an immutable Problem class and a fluent ProblemBuilder for convenient construction.
Note that RFC 7807 was later extended in RFC 9457, however core concepts remain the same.
It is intended to be used as a foundation for other libraries or applications that add framework-specific behavior (e.g. Jackson, Spring - see Problem4J Links chapter).
- ✅ Immutable
Problemdata model. - ✅ Dedicated unchecked
ProblemExceptionto be used in error handling. - ✅ Builder pattern for fluent construction.
- ✅ Static
Problem.of(...)factory methods for in-place creation convenience (sincev1.4.0). - ✅
@ProblemMappingannotation andProblemMapperto allow declarative approach in converting exception instances intoProblemobjects. - ✅ Serializable and easy to log or format.
- ✅ HTTP-agnostic (no external dependencies).
- ✅ Follows RFC 7807 semantics:
type(URI),title(short summary),status(numeric code),detail(detailed description),instance(URI to the specific occurrence),- custom field extensions.
- ✅ Integrated with JSpecify annotations for nullability and Kotlin interop (since
v1.4.0). - ✅ Supports Java version 8+, but due to producing multi-release JAR, can support Java Platform Module System if
using Java version 9+ (since
v1.4.0).
Throw an instance of ProblemException.
import io.github.problem4j.core.Problem;
import io.github.problem4j.core.ProblemException;
// using fully-fledged builder
Problem problem =
Problem.builder()
.type("https://example.com/errors/invalid-request")
.title("Invalid Request")
.status(400)
.detail("not a valid json")
.instance("https://example.com/instances/1234")
.build();
throw new ProblemException(problem);
// using convenience factory method
throw new ProblemException(Problem.of("Invalid Request", 400, "not a valid json"));Throw an exception annotated with @ProblemMapping.
import io.github.problem4j.core.ProblemMapping;
import io.github.problem4j.core.ProblemMapper;
@ProblemMapping(
type = "https://example.org/test/problem",
title = "Test problem",
status = 400,
detail = "failed: {message}",
extensions = {"subject"})
public class MessageException extends RuntimeException {
private final String subject;
public MessageException(String subject, String message) {
super(message);
this.subject = subject;
}
}
MessageException ex = new MessageException("sub", "boom");
ProblemMapper mapper = ProblemMapper.create();
Problem problem = mapper.toProblemBuilder(ex).build();If using Java module system, add the following requires directive to your module-info.java file.
module org.example.project {
requires io.github.problem4j.core;
}Add library as dependency to Maven or Gradle. See the actual versions on Maven Central. Java 8 or higher is required to use this library.
- Maven:
<dependencies> <dependency> <groupId>io.github.problem4j</groupId> <artifactId>problem4j-core</artifactId> <version>1.4.2</version> </dependency> </dependencies>
- Gradle (Groovy or Kotlin DSL):
dependencies { implementation("io.github.problem4j:problem4j-core:1.4.2") }
Problem4J Core is considered feature complete. Only bug fixes will be added. New features may be included only if there is a strong justification for them; otherwise, future projects are expected to build on this one as a dependency.
problem4j.github.io- Full documentation of all projects from Problem4J family.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 or higher to run. For building the project, Gradle automatically picks up Java
25 via toolchains and the foojay-resolver-convention plugin. This Java version is needed because the project
uses ErrorProne and NullAway for static nullness analysis.
The produced artifacts are compatible with Java 8 thanks to options.release = 8 in the Gradle JavaCompile task.
This means that regardless of the Java version used to run Gradle, the resulting bytecode remains compatible.
The default Gradle tasks include spotlessApply (for code formatting) and build (for compilation and tests).The
simplest way to build the project is to run:
./gradlewTo execute tests use test task. Tests do not change options.release so newer Java API can be used.
./gradlew testTo format the code according to the style defined in build.gradle.kts rules use spotlessApply
task. Note that building will fail if code is not properly formatted.
./gradlew spotlessApplyNote that if the year has changed, add -Pspotless.license-year-enabled flag to update the year in license headers.
The publishing GitHub Action will fail if the year is not updated, but
for local development and builds you can choose to skip it and update the year later.
./gradlew spotlessApply -Pspotless.license-year-enabledTo publish the built artifacts to local Maven repository, use publishToMavenLocal task.
./gradlew publishToMavenLocalNote that for using Maven Local artifacts in target projects, you need to add mavenLocal() repository.
repositories {
mavenLocal()
mavenCentral()
}