Quarkus and RFC 9457: Smarter Error Handling for Modern APIs
Go beyond vague 400s and 500s and learn how to implement structured, standards-based error responses in your Quarkus applications with just a few lines of code.
You might remember RFC 7807, which standardized a machine-readable format for error responses in HTTP APIs. RFC 9457 is the successor to RFC 7807, and it's fully backward-compatible.
The new spec encourages specific problem types and introduces a registry of common error categories. This avoids the usual flood of generic 400
and 500
responses, and gives clients more context to handle errors intelligently.
In this tutorial, we will build a simple Quarkus application that demonstrates how to implement RFC 9457-compliant error handling. Thanks to Zalando’s Problem library and the existing Quarkus extension, we can do this with only two classes and zero configuration.
Create a New Quarkus Application
First, let's create a new Quarkus project. You can use the Quarkus CLI, Maven, or your favorite IDE to do this. For this tutorial, we will use the Maven command.
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=rfc9457-tutorial \
-DclassName="com.example.GreetingResource" \
-Dpath="/hello" \
-Dextensions="rest-jackson, io.quarkiverse.resteasy-problem:quarkus-resteasy-problem"
cd rfc9457-tutorial
This command creates a new Quarkus project with the rest-jackson
extension, which is what we need for creating REST endpoints and handling JSON.
We are using Zalando’s Problem library that implements application/problem+json
. It comes with an extensible set of interfaces/implementations as well as convenient functions for every day use. It has a convenient Quarkiverse extension. Don’t get confused by the name. It does support quarkus-rest.
Create A Problem
A Problem is a structured error. It’s closely related to exceptions: in fact, you’ll usually transform one into the other.
Using HttpProblem
gives us a Problem
implementation that already extends RuntimeException
. That means we can throw it directly from resource methods.
package com.example;
import io.quarkiverse.resteasy.problem.HttpProblem;
import jakarta.ws.rs.core.Response;
public class OutOfStockProblem extends HttpProblem {
OutOfStockProblem(String message) {
super(builder()
.withTitle("Bad hello request")
.withStatus(Response.Status.BAD_REQUEST)
.withDetail(message)
.withHeader("X-RFC7807-Message", message)
.with("hello", "world"));
}
}
Notice the extras:
withHeader
adds an HTTP header alongside the JSON payload..with("hello", "world")
demonstrates a custom field. RFC 9457 explicitly allows extension members like this.
Create a Simple REST Endpoint
Now, let's create a simple REST endpoint that we can use to trigger our custom error. Open the GreetingResource.java
file and replace its content with the following:
package com.example;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response hello(@QueryParam("name") String name) {
if (name == null || name.isEmpty()) {
throw new OutOfStockProblem("The requested item is out of stock.");
}
return Response.ok("Hello " + name).build();
}
}
In this code, we have a simple endpoint that takes a name
as a query parameter. If the name
is not provided, we directly throw our custom problem called OutOfStockProblem
.
(And yes, delete the generated test classes under src/test
. I’ll save the testing story for a future instalment.)
Test the Implementation
Now, let's test our implementation. Start the Quarkus application in dev mode:
./mvnw quarkus:dev
Now, open your browser or use a tool like curl
to make a request to the /hello
endpoint without the name
query parameter:
curl -i http://localhost:8080/hello
You should see a response like this:
HTTP/1.1 400 Bad Request
content-length: 125
Content-Type: application/problem+json
X-RFC7807-Message: The requested item is out of stock.
{
"status": 400,
"title": "Bad hello request",
"detail": "The requested item is out of stock.",
"instance": "/hello",
"hello": "world"
}
Two things stand out:
A structured
application/problem+json
body with detail and extensions.A matching log entry for free:
2025-08-24 10:51:59,076 INFO [http-problem] (executor-thread-1) status=400, title="Bad hello request", detail="The requested item is out of stock.", hello="world"
OpenAPI integration
When quarkus-smallrye-openapi
is in the classpath, this extension provides a bunch of out-of-the-box features :
complete OpenApi schema definitions for
HttpProblem
andHttpValidationProblem
that can be used in annotations (e.g.@Schema(implementation = HttpProblem.class)
)auto-generating documentation for endpoints declaring
throws
for few common exceptions, e.g.NotFoundException
,ForbiddenException
or evenException
Conclusion
RFC 9457 makes API errors more predictable and more useful. With Quarkus and the resteasy-problem
extension, adopting it is almost effortless.
Instead of throwing vague RuntimeException
s or hand-crafting JSON, you get structured responses, standard headers, OpenAPI integration, and extensibility, all from a couple of lines of code.
The next time you’re about to send a bare 400
or 500
, stop. Define a proper problem, give it a type, and make your API errors as usable as your API features.