Quarkus RESTEasy Reactive vs Classic: Decision Guide
Choose between RESTEasy Reactive and Classic in Quarkus. Covers event-loop risks, @Blocking, dependency setup, and thread verification.
21 Oct 2025, 17:51 UTC

The Decision: RESTEasy Reactive or Classic for Your Quarkus API
Your Quarkus application needs a REST engine, but the choice between RESTEasy Reactive and RESTEasy Classic affects startup time, memory, and thread behavior before you write a single endpoint. The wrong choice can slow startup, increase memory use, or break custom filters that worked in older Java EE applications. This guide helps you decide based on your execution model, legacy dependencies, and deployment target.
The practical takeaway: use RESTEasy Reactive for new Quarkus applications. It is the current recommended default, runs on Vert.x event loops, and starts faster with lower memory, especially in native binaries. Reserve RESTEasy Classic for migrations where a specific JAX-RS filter, interceptor, or extension is incompatible with the reactive engine.
Comparison of Supported Options
| Decision factor | RESTEasy Reactive | RESTEasy Classic |
|---|---|---|
| Underlying engine | Vert.x event loop | Undertow worker thread pool |
| Default request handling | Non-blocking on event loop | Blocking on worker thread |
| Asynchronous return types | Mutiny Uni and Multi | Jakarta AsyncResponse |
| Native compilation (GraalVM) | Optimized, lower memory | Standard support |
| Custom JAX-RS filters/interceptors | May require audit and changes | Traditional JAX-RS behavior |
Trade-offs and Engineering Constraints
The choice depends on how your code performs I/O and whether you can tolerate a mixed threading model.
Event Loop Starvation
RESTEasy Reactive processes most requests on a small number of Vert.x event-loop threads. If a resource method performs a blocking operation—such as a synchronous JDBC query, file read, or external HTTP call—without declaring itself as blocking, that event-loop thread waits instead of handling other requests. Because the same thread serves many connections, one slow call can stall the entire application. This is called event-loop starvation.
The @Blocking Escape Hatch
Quarkus lets you keep imperative code by annotating a method with org.jboss.resteasy.reactive.Blocking. When present, Quarkus runs that method on a worker thread pool instead of the event loop. You do not have to rewrite blocking business logic into Mutiny streams, but you should annotate every blocking method explicitly.
Legacy Extension Compatibility
RESTEasy Classic is the older, thread-per-request implementation. Custom filters or interceptors written for classic JAX-RS often migrate more easily to Classic than to Reactive. However, Classic uses a worker thread for every request regardless of return type, which increases memory overhead and slows startup compared with Reactive.
Implementation: Enable RESTEasy Reactive
Add the reactive dependency to your Maven pom.xml. Do not include both quarkus-resteasy and quarkus-resteasy-reactive in the same project; the combination can cause classpath conflicts.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
Run this change in your project directory with write access to the build file. After editing, recompile with ./mvnw compile (Linux/macOS) or ./mvnw.cmd compile (Windows). There is no database migration, but the application will fail to start if both RESTEasy artifacts remain on the classpath.
Example: Mixed Blocking and Non-Blocking Endpoints
The following resource shows a fast endpoint handled on the event loop, a blocking endpoint offloaded to workers, and an asynchronous endpoint using Mutiny. Replace OrderRepository and ReactiveOrderClient with your own data access classes.
import org.jboss.resteasy.reactive.Blocking;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/api/orders")
public class OrderResource {
private final OrderRepository repository;
private final ReactiveOrderClient reactiveClient;
public OrderResource(OrderRepository repository, ReactiveOrderClient reactiveClient) {
this.repository = repository;
this.reactiveClient = reactiveClient;
}
// Non-blocking: runs on the Vert.x event loop
@GET
@Path("/status")
@Produces(MediaType.TEXT_PLAIN)
public String status() {
return "running";
}
// Blocking: offloaded to a worker thread
@GET
@Path("/{id}")
@Blocking
@Produces(MediaType.APPLICATION_JSON)
public Order findById(@PathParam("id") String id) {
return repository.findById(id);
}
// Asynchronous: returns a Mutiny Uni
@GET
@Path("/reactive/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni findReactive(@PathParam("id") String id) {
return reactiveClient.findById(id);
}
}
Verification: Confirm the Active Engine and Thread
Start the application and inspect the console log. RESTEasy Reactive logs indicate the Vert.x-based engine during startup. To confirm which thread handles a request, add a temporary diagnostic line inside a resource method:
System.out.println("Thread: " + Thread.currentThread().getName());
Expected observations:
- RESTEasy Reactive without
@Blocking: thread name typically includesvert.x-event-loop. - RESTEasy Reactive with
@Blocking: thread name typically includesexecutororworker. - RESTEasy Classic: thread name typically includes
undertow-worker.
These thread names depend on the Quarkus and Vert.x version; treat them as indicators, not guarantees.
Rollback to RESTEasy Classic
Changing the build dependency alters application state, so a rollback path is useful if custom filters fail under Reactive.
- Remove
quarkus-resteasy-reactivefrompom.xml. - Add
quarkus-resteasy. - Remove
@Blockingannotations; Classic blocks by default and ignores the annotation. - Recompile and restart the application.
Limitations
This guide assumes Quarkus 3.x with Jakarta EE 10 APIs. Thread-name patterns are not part of a public API and may change between versions. The example is not tested against a live database or external service. Verify your specific custom filters and interceptors before adopting Reactive in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.