RESTEasy Reactive vs Hibernate ORM: Are @NonBlocking endpoints safe to call blocking JPA?
0 reputation · 25 Mar 2026, 20:11 UTC
0 reputation · 25 Mar 2026, 20:11 UTC
In a Quarkus application that mixes RESTEasy Reactive and Hibernate ORM, the goal is to determine whether synchronous repository calls—such as PanacheRepository.save()—executed inside a @NonBlocking resource method are automatically migrated off the Vert.x event‑loop. The main constraint is that Hibernate’s JDBC interactions are inherently blocking; if they run on the event‑loop, they can stall all reactive traffic.
Quarkus dispatches @NonBlocking resource methods onto the Vert.x event‑loop, while @Blocking methods are scheduled on worker threads. However, the documentation does not explicitly state whether a blocking call made within a @NonBlocking method triggers an automatic thread migration.
Another uncertainty is the interaction with @Transactional boundaries: does the transaction’s flush occur before the resource method returns, or only at commit time? This affects visibility of persisted entities to other concurrent requests.
Given these ambiguities, the following questions remain unresolved:
@NonBlocking RESTEasy Reactive endpoint is automatically offloaded to a worker thread?@Transactional boundary ensure that flushes occur before the endpoint method completes, or only at transaction commit?In Quarkus, @NonBlocking resource methods are executed directly on the Vert.x event-loop. Because Hibernate ORM relies on JDBC—which is inherently synchronous and blocking—calling a repository method like PanacheRepository.save() inside a non-blocking method will stall the event-loop. This prevents the loop from processing other concurrent requests, leading to severe latency and potential application-wide timeouts.
Quarkus does not automatically detect or migrate blocking calls within a @NonBlocking method to a worker thread. To use blocking Hibernate operations in a reactive environment, you must explicitly use the @Blocking annotation. This tells Quarkus to dispatch the specific method execution to a managed worker thread pool, keeping the event-loop free for other traffic.
@Path("/items")
public class ItemResource {
@Inject
ItemRepository repository;
@POST
@NonBlocking
@Blocking // Required to offload the blocking JDBC call
public Uni create(Item item) {
return Uni.createFrom().item(() -> repository.save(item));
}
}
Transactional Boundaries and Flushing
The @Transactional boundary in Quarkus is tied to the current thread. In a standard synchronous execution, the transaction flushes and commits when the method completes successfully. When mixing @NonBlocking and @Blocking, the transaction remains on the worker thread. The flush occurs before the method returns its result to the reactive stream, ensuring that persisted entities are visible to subsequent requests once the reactive stream completes.
nSummary of Constraints
@Blocking manually.@NonBlocking without offloading.Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 26 Mar 2026, 03:07 UTC
Quarkus dispatches @NonBlocking resource methods onto the Vert.x event‑loop. If a blocking JDBC call—such as PanacheRepository.save()—is executed inside that method, the event‑loop thread is held until the call completes. Quarkus does not automatically migrate the call to a worker thread; you must explicitly annotate the method with @Blocking or wrap the call in Uni.createFrom().runOnWorkerThread(...).
When @Transactional surrounds the method, the persistence context flushes only at transaction commit, which occurs after the resource method returns. Consequently, other concurrent requests may not see the newly persisted entity until the commit has finished.
@GET
@NonBlocking
@Transactional
public Uni<Response> create(@Valid Item item) {
return Uni.createFrom().item(itemRepository::save)
.onItem().transform(saved -> Response.ok(saved).build());
}