Handling errors
Right now a request for a missing post returns 404 with an empty body. An API client is better served by an explanation: what went wrong and with what. For that the code throws its own exception, and an exception mapper turns it into an HTTP response with the right status and body. The error logic is then kept in one place, and resources stay short.
Your own exception
// src/main/java/com/example/blog/PostNotFoundException.java
package com.example.blog;
public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(long id) {
super("Post " + id + " not found");
}
}The exception extends RuntimeException, so it does not have to be declared in throws. For the response body we add a record:
// src/main/java/com/example/blog/ErrorMessage.java
package com.example.blog;
public record ErrorMessage(String error) {
}The mapper
In Quarkus REST a mapper is a method annotated with @ServerExceptionMapper. The parameter type tells which exception it handles:
// src/main/java/com/example/blog/ExceptionMappers.java
package com.example.blog;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
public class ExceptionMappers {
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapNotFound(PostNotFoundException e) {
return RestResponse.status(Response.Status.NOT_FOUND, new ErrorMessage(e.getMessage()));
}
}RestResponse<ErrorMessage> is the typed response of Quarkus REST: a status plus an object that becomes JSON. The class with mappers can live anywhere in the project — Quarkus finds it at build time.
Throwing the exception in the resource
In PostResource replace NotFoundException with your own exception:
// src/main/java/com/example/blog/PostResource.java
@GET
@Path("/{id}")
public Post get(@PathParam("id") long id) {
return postService.findById(id)
.orElseThrow(() -> new PostNotFoundException(id));
}
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") long id) {
if (!postService.delete(id)) {
throw new PostNotFoundException(id);
}
return Response.noContent().build();
}curl -i http://localhost:8080/posts/99HTTP/1.1 404 Not Found
Content-Type: application/json;charset=UTF-8
{"error":"Post 99 not found"}
The standard variant
In the Jakarta REST standard a mapper is a separate class annotated with @Provider that implements ExceptionMapper. Quarkus supports it as well:
// src/main/java/com/example/blog/IllegalArgumentMapper.java
package com.example.blog;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class IllegalArgumentMapper implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorMessage(e.getMessage()))
.build();
}
}Now any IllegalArgumentException from a resource becomes a 400 response with the JSON {"error":"..."}. The @ServerExceptionMapper variant is shorter, so Quarkus code mostly uses that one.
Which mapper runs
If several mappers fit, Quarkus picks the one whose type is closest to the thrown exception. Validation errors and standard exceptions such as NotFoundException already have built-in mappers.
Common mistake
It is tempting to write one mapper for everything — for Exception:
// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Something went wrong"));
}But it also catches the built-in Jakarta REST exceptions:
curl -i http://localhost:8080/nothing-here
curl -i -X PUT http://localhost:8080/posts/1HTTP/1.1 500 Internal Server Error
Content-Type: application/json;charset=UTF-8
{"error":"Something went wrong"}
Both requests should have returned 404 and 405, but got 500. Write mappers for your own specific exceptions. If you do need a general mapper, let the standard errors through with their own status:
// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
if (e instanceof WebApplicationException w) {
return RestResponse.status(w.getResponse().getStatusInfo());
}
return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Something went wrong"));
}With this check (import jakarta.ws.rs.WebApplicationException) an unknown address gets 404 again, and a wrong method gets 405.