problem: RFC 7807 error responses for Java services
A Java library that implements application/problem+json
At a glance
- What is it?
- problem is a small Java library that implements the application/problem+json format for REST APIs, with three levels of expressiveness ranging from a status code to a fully custom problem type, and it is decoupled from any JSON library so the core has no serialization dependency at all. It spent four years on a 0.x line before cutting 1.0.0 in May 2026, which tells you what the maintainers thought of the API's stability.
- Who is it for?
- Adopt problem when you run REST services in Java and want error responses that follow a published media type rather than a house format, since the library gives you the format, the builder and the exception bridge in one dependency with no JSON library forced on you.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 91 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A media type for errors, and what the library covers
problem is a library that implements application/problem+json, the media type defined by RFC 7807, and it comes with an extensible set of interfaces and implementations as well as convenient functions for everyday use. The features are stated in two lines: it proposes a common approach for expressing errors in REST API implementations, and it is compatible with the media type.
That is a narrow scope, and it is the right one. RFC 7807 defines a JSON object with a small set of members, type, title, status, detail and instance, where the type is a URI that identifies the problem type and therefore lets a client react to a class of failure rather than to a string. A service that returns those members with a stable type URI gives its consumers something to program against, which is the problem this library exists to solve.
The word decoupled in the README is the important qualifier. The library is decoupled from any JSON library, but contains a separate module for Jackson. So the core artifact, which is the model and the type hierarchy, does not drag a serialization framework into your dependency tree, and the JSON support is opt-in through a module you add if you want it.
The name and the definition in the README are a small joke that also tells you the tone: problem, a difficulty that has to be resolved or dealt with, with a pronunciation guide. The project also has a Bubble Gum on Shoe image in the docs, which is a reminder that this is a library with a sense of humour, built by Zalando, the company whose engineering blog and open source work are well known in the Java ecosystem.
The project is MIT licensed, published to Maven Central under the org.zalando group, and the homepage is zalando.github.io/problem.
The dependency set and the four modules
The dependencies are listed as Java 17, any build tool using Maven Central or direct download, and three optional JSON libraries: Jackson2, Jackson3 and Gson.
The split into modules follows from the decoupling. The repository has four module directories, problem for the core, problem-jackson2, problem-jackson3 and problem-gson for the serializers, and the Maven coordinates in the README show all three flavours:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem</artifactId>
<version>${problem.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-jackson3</artifactId>
<version>${problem.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-gson</artifactId>
<version>${problem.version}</version>
</dependency>The fact that Jackson 3 has its own module is the kind of detail that dates the project. Jackson 3 is a major rewrite of the Jackson databind stack, and a library that serializes a small model has to be rebuilt against it rather than carried forward, so the presence of problem-jackson3 alongside problem-jackson2 is the maintenance of an existing library rather than a new feature.
The modules are also fully compatible with the Java Platform Module System, and the README shows how a consumer declares them:
module org.example {
requires org.zalando.problem;
// pick needed dependencies
requires org.zalando.problem.jackson;
requires org.zalando.problem.gson;
}The comment about picking needed dependencies is the point of the example, since the JSON module is optional and a modular consumer declares only the one it uses.
Three levels of expressiveness, from a status code to a custom type
The usage section describes different ways to express problems, ranging from limited but easy to use, to highly flexible and extensible but with slightly more effort. That is an accurate summary of the three levels the library offers.
The generic level takes an HTTP status code and builds a problem from it:
var problem = Problem.valueOf(Status.NOT_FOUND);which produces a title and a status, and nothing else. The README quotes the specification on this point: the about:blank URI, when used as a problem type, indicates that the problem has no additional semantics beyond that of the HTTP status code, and when about:blank is used the title should be the same as the recommended HTTP status phrase for that code, for example Not Found for 404, although it may be localized to suit client preferences expressed with the Accept-Language request header.
That quotation is the library being honest about a weakness in the format itself, and it points at a real question for an implementer: a bare status code carries no information a client can act on beyond the code it would have seen anyway.
The middle level adds a hint while keeping the generic shape:
var problem = Problem.valueOf(Status.SERVICE_UNAVAILABLE, "Database not reachable");which adds a detail member to the response.
The builder level is where application specific problem types appear, with a fluent API that avoids writing a class:
var problem = Problem.builder()
.withType(URI.create("https://example.org/out-of-stock"))
.withTitle("Out of Stock")
.withStatus(BAD_REQUEST)
.withDetail("Item B00027Y5QG is no longer available")
.build();and the builder also accepts extra members that are not part of the five standard ones, which is what lets a response carry a domain specific field such as the affected product identifier.
Custom problem types, and the exception bridge
The highest degree of flexibility comes from implementing Problem directly, which the README recommends when you refer to it in a lot of places, since that makes it easier to share. The alternative is extending AbstractThrowableProblem:
@Immutable
public final class OutOfStockProblem extends AbstractThrowableProblem {
static final URI TYPE = URI.create("https://example.org/out-of-stock");
private final String product;
public OutOfStockProblem(final String product) {
super(TYPE, "Out of Stock", BAD_REQUEST, format("Item %s is no longer available", product));
this.product = product;
}
public String getProduct() {
return product;
}
}The type URI is a constant on the class, which is the right way to do it, because the URI is the contract with the client and a typo in one of many throw sites is exactly the bug this class is meant to prevent. The @Immutable annotation and the final class and the final field are the shape a value type should have, and the detail string is built in the constructor from the product identifier.
The exception connection is what the README calls a loose but direct connection between problems and exceptions, and says that most of the time you will find yourself transforming one into the other. AbstractThrowableProblem is the abstract Problem implementation that subclasses RuntimeException, and the point of that is that a problem can be thrown. The library does not pretend the two are the same thing, it just makes the conversion cheap.
For a service that uses typed exceptions, the practical workflow is to throw the domain exception, map it to a problem type at the boundary, and serialise the problem. Each of those three steps is a place where a service can be inconsistent, and the library's job is to make the middle step one call rather than a hand-written mapping in every controller.
Wiring the serializer, by module or by service discovery
If you use Jackson, the README says to make sure you register the module with your JsonMapper:
JsonMapper mapper = JsonMapper.builder().addModule(new ProblemModule()).build();Alternatively you can use the SPI capabilities:
JsonMapper mapper = JsonMapper.builder().findAndAddModules().build();The two routes are a real choice, and the second one has consequences worth thinking about. Registering the module explicitly means the mapper that serializes problems is one you constructed on purpose, and a reader can see the dependency. Using findAndAddModules means the mapper picks up every module on the classpath, which is convenient and also means the presence of the problem module changes the behaviour of mappers you did not think about, including ones built by a framework.
The explicit form is the better default in a service where you control the mapper, and the SPI form is the right choice when the mapper is built by a framework that owns the configuration and you would rather not reach into it.
The Gson module has no equivalent ceremony, because Gson's model is simpler and the library can register through its own mechanism, and the fact that the core has no JSON dependency at all is what keeps the Gson and Jackson paths from fighting each other in a project that has both.
Release history, project hygiene and the judgement to make
The release record is unusual and worth reading closely. The most recent release is 1.0.0 on 2026-05-22, and before that the tags are 0.27.1 and 0.27.0, both on 2022-01-07.
So the project spent most of its public life on a 0.x line, ran 0.27 for a long time, and then cut 1.0.0 more than four years later. That is a deliberate statement that the API is now considered stable, and it is also a signal that a lot of things changed between 0.27 and 1.0, so an upgrade from an old version is a migration rather than a bump. The last push was on 2026-07-24 and the repository is not archived.
The project hygiene is visible in the tree. There is a .java-version file, the Maven wrapper with mvnw and mvnw.cmd, a .mvn directory, a release.sh script, a MAINTAINERS file, a SECURITY.md, a .mailmap, a .zappr.yaml, which is the configuration for the pull request approval bot that Zalando uses, and a cve-suppressions.xml file.
That last file is the one to notice. A library that depends on Jackson or Gson inherits a supply chain of transitive dependencies, and a file that records which known CVEs are deliberately not fixed is a deliberate, auditable statement about that risk rather than a silent omission. For a library that many services depend on for their error responses, that is a good thing to have in the tree.
The badges tell the rest of the story, with a stability badge reading Sustained, a build status badge, a coverage report on Coveralls, a Codacy code quality grade, a Javadoc badge and a Maven Central release badge.
The judgement is straightforward. If your service returns errors that clients have to understand, this library gives you the media type, the type hierarchy, the builder, the exception bridge and the serializers, with a core dependency that is small and a JSON library you choose. What it does not give you is a policy for error codes, a catalogue of error types for your domain, or localisation of the title, and if you need those you will still be writing them.
Editorial conclusion
Adopt problem when you run REST services in Java and want error responses that follow a published media type rather than a house format, since the library gives you the format, the builder and the exception bridge in one dependency with no JSON library forced on you. It is the wrong tool if you need problem details for gRPC or GraphQL error shapes, and it is the wrong tool if you want a full error framework with error codes, localisation and stack trace handling, since this one is deliberately only the response format. Before upgrading from a 0.x version, read the 1.0.0 release notes, because cutting the major version after four years of 0.27.x releases is a signal that something in the API changed.
Frequently asked questions
What does the Zalando problem library implement?
It implements the application/problem+json format defined by RFC 7807, which is a common approach for expressing errors in REST API implementations. The core is decoupled from any JSON library, with separate modules for Jackson 2, Jackson 3 and Gson.
What are the dependencies for using the problem library?
Java 17, a build tool using Maven Central or a direct download, and optionally Jackson2, Jackson3 or Gson. The Maven coordinates are org.zalando:problem for the core and separate artifacts for each serializer, and the modules are also usable with the Java Platform Module System.
How do I create a problem with extra fields?
Use the builder and the with method for members other than type, title, status, detail and instance, for example with("product", "B00027Y5QG"). For a type you refer to often, implement Problem directly or extend AbstractThrowableProblem so the type URI is a constant on the class.
How do I register the Jackson support?
Register the module explicitly with JsonMapper.builder().addModule(new ProblemModule()).build(), or use the service provider capabilities with JsonMapper.builder().findAndAddModules().build(). The explicit form is easier to reason about because the mapper's behaviour is visible where it is built.
Can a problem be thrown as an exception?
Yes. AbstractThrowableProblem is an abstract Problem implementation that subclasses RuntimeException, so a problem can be thrown, and the README describes the relationship between problems and exceptions as loose but direct, with most of the work being the transformation between the two.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zalando-problem)Was this useful?