quarkusio/quarkus · error · IllegalStateException
Blocking gRPC client call made from the event loop. If the c
Error message
Blocking gRPC client call made from the event loop. If the code is executed from a gRPC service or a RESTEasy Reactive resource, either annotate the method that makes the call with `@Blocking` or use the non-blocking client.
What it means
EventLoopBlockingCheckInterceptor is a gRPC client interceptor that detects blocking client calls made on an event-loop thread, which would stall other requests sharing that thread. When Context.isOnEventLoopThread() is true it throws IllegalStateException, advising to use @Blocking or the non-blocking (mutiny) client.
Source
Thrown at extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/EventLoopBlockingCheckInterceptor.java:15
package io.quarkus.grpc.runtime.supports;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.MethodDescriptor;
import io.vertx.core.Context;
public class EventLoopBlockingCheckInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
Channel next) {
if (Context.isOnEventLoopThread()) {
throw new IllegalStateException("Blocking gRPC client call made from the event loop. " +
"If the code is executed from a gRPC service or a RESTEasy Reactive resource, either annotate the method " +
" that makes the call with `@Blocking` or use the non-blocking client.");
}
return next.newCall(method, callOptions);
}
}
View on GitHub (pinned to e1c734241f)
Solutions
- Annotate the calling method with @Blocking so it runs on a worker thread
- Use the Mutiny (non-blocking) stub instead of the blocking stub and compose with Uni/Multi
- Move the blocking call off the event loop (e.g. execute on an executor)
- Ensure @RunOnVirtualThread/@Blocking semantics fit before catching/suppressing
Example fix
// before
@GET
public Uni<String> get() {
return Uni.createFrom().item(blockingStub.sayHello(req).getMessage()); // event loop!
}
// after
@GET
public Uni<String> get() {
return mutinyStub.sayHello(req).map(HelloReply::getMessage);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (Context.isOnEventLoopThread() && usingBlockingStub) {
LOG.warn("Blocking gRPC call about to run on event loop; switch to Mutiny stub or @Blocking");
} Try / catch
try {
return blockingStub.sayHello(req);
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Blocking gRPC client call")) {
throw new ServerWebApplicationException("Use non-blocking client", 500);
}
throw e;
} Prevention
- Prefer Mutiny stubs in reactive endpoints
- Annotate methods performing blocking calls with @Blocking
- Check Context.isOnEventLoopThread() in dev-mode tests
When it happens
Trigger: Invoking a blocking gRPC stub method (blocking variant of a Quarkus gRPC client) from inside a method running on an event-loop thread — e.g. from a reactive gRPC service method, a RESTEasy Reactive resource method, or a vertx event-loop callback.
Common situations: Calling blockingService.method() inside a @GET reactive endpoint; awaiting a gRPC call inside a Mutiny pipeline on the event loop; legacy blocking code moved into a reactive handler.
Related errors
- @Transactional cannot start a JTA transaction within a react
- Attempting a blocking read on io thread
- You have attempted to perform a blocking operation on a IO t
- @PermissionChecker annotation instance placed on the '%s' re
- Attempting a blocking read on io thread
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/b598c4759936b017.
Report an issue: GitHub.