This episode builds a REST API with RESTEasy Reactive: mapping requests with @GET, @POST, @PUT, @DELETE and @Path, binding bodies and response objects, up to error handling with a custom exception mapper.

After mastering CDI, it's time to build the interface most Quarkus applications use the most: a REST API. Quarkus uses RESTEasy Reactive — a Vert.x-based JAX-RS implementation that is non-blocking and tightly integrated with build-time augmentation.
Episode 5 covers the foundations of a REST API: mapping endpoints with @GET, @POST, @PUT, @DELETE, and @Path, binding JSON bodies to Java objects, producing response objects, and error handling with a custom exception mapper.
RESTEasy Reactive uses the standard JAX-RS annotations. A simple resource class:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/api/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String halo() {
return "Halo Quarkus!";
}
}With the resteasy-reactive-jackson extension, JSON is also supported:
./mvnw quarkus:add-extension -Dextensions=resteasy-reactive-jacksonFor a real API, return a Java object that's automatically serialized to JSON:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/api/greeting")
public class GreetingResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public GreetingResponse greeting() {
return new GreetingResponse("Halo", "Quarkus");
}
public record GreetingResponse(String message, String to) {}
}The @GET method returns a Java record that Jackson automatically serializes to JSON. Run it with ./mvnw quarkus:dev then test it with curl.
RESTEasy Reactive maps every HTTP method:
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import java.util.ArrayList;
@Path("/api/items")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ItemResource {
private final List<Item> items = new ArrayList<>();
@GET
public List<Item> list() { return items; }
@GET
@Path("/{id}")
public Item get(@PathParam("id") long id) {
return items.stream()
.filter(i -> i.id() == id)
.findFirst()
.orElseThrow(() -> new NotFoundException("Item tidak ditemukan"));
}
@POST
public Item create(Item item) {
items.add(item);
return item;
}
@PUT
@Path("/{id}")
public Item update(@PathParam("id") long id, Item item) {
items.set((int) id, item);
return item;
}
@DELETE
@Path("/{id}")
public void delete(@PathParam("id") long id) {
items.removeIf(i -> i.id() == id);
}
public record Item(long id, String nama) {}
}The @Path("/{id}") annotation captures a path segment, and @PathParam("id") maps it to a method parameter. This is the standard pattern for resources with path parameters.
A parameter without any annotation on a POST method is automatically bound from the JSON body. @GET methods cannot have a body, while @POST, @PUT, and @PATCH use the request body. Besides @PathParam, JAX-RS also provides @QueryParam for query strings and @HeaderParam for headers. @Produces and @Consumes control the content format, for example MediaType.APPLICATION_JSON for JSON.
JAX-RS provides an exception mapper mechanism to translate exceptions into clean HTTP responses:
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class ItemNotFoundMapper implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException ex) {
return Response.status(Response.Status.NOT_FOUND)
.entity(new ErrorResponse(404, ex.getMessage()))
.build();
}
public record ErrorResponse(int status, String message) {}
}With @Provider and the ExceptionMapper<T> implementation, every exception of type NotFoundException is translated into a 404 response with a structured JSON body.
Consistency in the error format matters for APIs used by many clients. Create one centralized error format and register mappers for global exceptions. Test the error handling with curl:
curl http://localhost:8080/api/items
curl -i http://localhost:8080/api/items/999The command curl http://localhost:8080/api/items returns the list of items as JSON, while a request to a non-existent item returns a structured error body with status 404.
Episode 5 builds the foundation of your REST API: creating resources with RESTEasy Reactive, mapping all HTTP methods and path parameters, binding JSON bodies to Java objects, producing response objects, and handling errors with a custom exception mapper.
Key takeaways:
@Path("/{id}") together with @PathParam captures path segments.@GET has no body; @POST, @PUT, @PATCH use the body.@Produces and @Consumes control the content format.records are automatically serialized to JSON.ExceptionMapper handles errors with a centralized format.In episode 6 we'll connect your application to a database — Hibernate ORM and Panache, entity mapping, the repository pattern, datasource and connection pooling configuration, as well as the in-memory H2 database for development.