quarkusio/quarkus · error · BlockingOperationNotAllowedException
You have attempted to inject AuthzClient on a IO thread. Thi
Error message
You have attempted to inject AuthzClient on a IO thread. This is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required. Make sure you are injecting AuthzClient from a worker thread.
What it means
The Keycloak PEP throws BlockingOperationNotAllowedException when AuthzClient is injected (e.g. as a CDI bean into a resource) and resolved on a Vert.x IO/event-loop thread while the PolicyEnforcer is dynamically resolved, because obtaining it requires blocking calls that must not run on the IO thread.
Source
Thrown at extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerAuthorizer.java:112
routingContext = Arc.container().instance(CurrentVertxRequest.class).get().getCurrent();
}
if (routingContext != null && routingContext.get(POLICY_ENFORCER) != null) {
return routingContext.<PolicyEnforcer> get(POLICY_ENFORCER).getAuthzClient();
} else if (BlockingOperationControl.isBlockingAllowed()) {
OidcTenantConfig tenantConfig = routingContext == null ? null
: routingContext.get(OidcTenantConfig.class.getName());
return resolver.resolvePolicyEnforcer(routingContext, tenantConfig)
.await().indefinitely()
.getAuthzClient();
} else {
if (resolver instanceof DefaultPolicyEnforcerResolver defaultResolver
&& !defaultResolver.hasDynamicPolicyEnforcers()) {
return defaultResolver.getStaticPolicyEnforcer(identity.getAttribute(TENANT_ID_ATTRIBUTE)).getAuthzClient();
} else {
// this shouldn't happen inside HTTP request as policy enforcer is in most cases accessible from context
// and the Authz client itself is blocking so users can as well inject it when on the worker thread
throw new BlockingOperationNotAllowedException("""
You have attempted to inject AuthzClient on a IO thread.
This is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required.
Make sure you are injecting AuthzClient from a worker thread.
""");
}
}
}
private Uni<CheckResult> checkPermissionInternal(RoutingContext routingContext, SecurityIdentity identity) {
AccessTokenCredential credential = identity.getCredential(AccessTokenCredential.class);
if (credential == null) {
// SecurityIdentity has been created by the authentication mechanism other than quarkus-oidc
return CheckResult.permit();
}
VertxHttpFacade httpFacade = new VertxHttpFacade(routingContext, credential.getToken(), resolver.getReadTimeout());
return resolver.resolvePolicyEnforcer(routingContext, routingContext.get(OidcTenantConfig.class.getName()))View on GitHub (pinned to e1c734241f)
Solutions
- Inject or access AuthzClient from a worker thread: annotate the endpoint with @Blocking or return a plain (blocking) response type.
- Obtain the AuthzClient lazily inside a Uni created on a worker thread (emitOn/Infrastructure) or use Mutiny's runSubscriptionOn.
- If dynamic enforcers are not needed, use static policy-enforcer configuration so the static resolver path (IO-thread safe) is used.
Example fix
// before
@Inject AuthzClient authzClient; // resolved on IO thread in reactive endpoint
@GET
public Uni<String> get() { ... }
// after
@GET
@Blocking
public String get() { authzClient.protection().resource().list(); ... } Defensive patterns
Strategy: validation
Validate before calling
// guard injection points: only access AuthzClient off the event loop
if (Vertx.currentContext() != null && Vertx.currentContext().isEventLoopContext()) {
throw new IllegalStateException("Inject AuthzClient from a worker thread (@Blocking or Uni emitOn)");
} Try / catch
try {
AuthzClient c = authzClient; // injected
c.protection().resource().findById(id);
} catch (BlockingOperationNotAllowedException e) {
log.error("Move AuthzClient usage to a worker thread (@Blocking) or use static policy enforcers", e);
throw e;
} Prevention
- Annotate endpoints that touch AuthzClient with @Blocking or use blocking return types.
- Avoid enabling dynamic policy enforcers unless per-tenant resolution is required.
- In reactive pipelines, offload with emitOn/Infrastructure before calling the Authz client.
- Review new injection points of AuthzClient during code review for reactive endpoints.
When it happens
Trigger: Injecting AuthzClient (or calling KeycloakPolicyEnforcerAuthorizer.getAuthzClient) from a method executing on the event-loop thread — e.g. a REST resource method returning Uni/Multi (reactive) without @Blocking, while dynamic policy enforcers are configured (per-tenant / dynamic policy enforcer resolver).
Common situations: Mixing reactive endpoints (returning Uni) with the policy-enforcer extension and injecting AuthzClient directly; enabling dynamic tenant/policy-enforcer resolution; moving from static to dynamic policy enforcer config without adjusting endpoint threading.
Related errors
- You have attempted to perform a blocking operation on a IO t
- Attempting a blocking write on io thread
- VertxContextSupport#subscribeAndAwait() must not be called o
- Attempting a blocking read on io thread
- Attempting a blocking read on io thread
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/c87763dd4b7dca58.
Report an issue: GitHub.