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

  1. Annotate the calling method with @Blocking so it runs on a worker thread
  2. Use the Mutiny (non-blocking) stub instead of the blocking stub and compose with Uni/Multi
  3. Move the blocking call off the event loop (e.g. execute on an executor)
  4. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/b598c4759936b017. Report an issue: GitHub.