A reactive return type does not guarantee that every operation inside a method is nonblocking. Quarkus normally dispatches methods returning reactive types to an event-loop thread, where blocking work can prevent other requests from progressing. Put @Blocking on a method that must perform such work to have Quarkus run it on a worker thread instead. section 8.1.1 section 8.4.1
A gRPC inventory service illustrates the distinction. Its add and remove methods use reactive types, but database inserts and deletes can block. Both methods therefore need @Blocking. The annotation changes where the methods execute; it does not turn their database operations into nonblocking I/O. section 7.2.2 section 8.4.2
Listing 8.5 The GrpcInventoryService#add method
@Override
@Blocking
public Multi<CarResponse> add(Multi<InsertCarRequest> requests) {
return requests
.map(request -> {
Car car = new Car();
car.setLicensePlateNumber(request.getLicensePlateNumber());
car.setManufacturer(request.getManufacturer());
car.setModel(request.getModel());
return car;
}).onItem().invoke(car -> {
QuarkusTransaction.requiringNew().run( () -> {
carRepository.persist(car);
Log.info("Persisting " + car);
});
}).map(car -> CarResponse.newBuilder()
.setLicensePlateNumber(car.getLicensePlateNumber())
.setManufacturer(car.getManufacturer())
.setModel(car.getModel())
.setId(car.getId())
.build());
}
The add method is annotated @Blocking even though it returns Multi<CarResponse>. Its pipeline maps each request to a Car, persists it through the repository, then maps it to a response. section 8.4.2
Worker-thread dispatch and transaction boundaries solve different problems. In the streaming add method, QuarkusTransaction.requiringNew().run() gives each persist operation its own transaction. Making the entire stream one transaction would leave it open until the stream closed, risking a bottleneck or timeout. Conversely, a genuinely nonblocking operation can remain on the event loop; @NonBlocking provides the opposite scheduling hint. section 7.2.2 section 8.1.1
