{"record":{"id":"c87763dd4b7dca58","repo":"quarkusio/quarkus","slug":"you-have-attempted-to-inject-authzclient-on-a-io-t","errorCode":null,"errorMessage":"You have attempted to inject AuthzClient on a IO thread.\nThis is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required.\nMake sure you are injecting AuthzClient from a worker thread.\n","messagePattern":"You have attempted to inject AuthzClient on a IO thread\\.\nThis is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required\\.\nMake sure you are injecting AuthzClient from a worker thread\\.\n","errorType":"exception","errorClass":"BlockingOperationNotAllowedException","httpStatus":null,"severity":"error","filePath":"extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerAuthorizer.java","lineNumber":112,"sourceCode":"            routingContext = Arc.container().instance(CurrentVertxRequest.class).get().getCurrent();\n        }\n\n        if (routingContext != null && routingContext.get(POLICY_ENFORCER) != null) {\n            return routingContext.<PolicyEnforcer> get(POLICY_ENFORCER).getAuthzClient();\n        } else if (BlockingOperationControl.isBlockingAllowed()) {\n            OidcTenantConfig tenantConfig = routingContext == null ? null\n                    : routingContext.get(OidcTenantConfig.class.getName());\n            return resolver.resolvePolicyEnforcer(routingContext, tenantConfig)\n                    .await().indefinitely()\n                    .getAuthzClient();\n        } else {\n            if (resolver instanceof DefaultPolicyEnforcerResolver defaultResolver\n                    && !defaultResolver.hasDynamicPolicyEnforcers()) {\n                return defaultResolver.getStaticPolicyEnforcer(identity.getAttribute(TENANT_ID_ATTRIBUTE)).getAuthzClient();\n            } else {\n                // this shouldn't happen inside HTTP request as policy enforcer is in most cases accessible from context\n                // and the Authz client itself is blocking so users can as well inject it when on the worker thread\n                throw new BlockingOperationNotAllowedException(\"\"\"\n                        You have attempted to inject AuthzClient on a IO thread.\n                        This is not allowed when PolicyEnforcer is resolved dynamically as blocking operations are required.\n                        Make sure you are injecting AuthzClient from a worker thread.\n                        \"\"\");\n            }\n        }\n    }\n\n    private Uni<CheckResult> checkPermissionInternal(RoutingContext routingContext, SecurityIdentity identity) {\n        AccessTokenCredential credential = identity.getCredential(AccessTokenCredential.class);\n\n        if (credential == null) {\n            // SecurityIdentity has been created by the authentication mechanism other than quarkus-oidc\n            return CheckResult.permit();\n        }\n\n        VertxHttpFacade httpFacade = new VertxHttpFacade(routingContext, credential.getToken(), resolver.getReadTimeout());\n        return resolver.resolvePolicyEnforcer(routingContext, routingContext.get(OidcTenantConfig.class.getName()))","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerAuthorizer.java#L94-L130","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n@Inject AuthzClient authzClient; // resolved on IO thread in reactive endpoint\n@GET\npublic Uni<String> get() { ... }\n// after\n@GET\n@Blocking\npublic String get() { authzClient.protection().resource().list(); ... }","handlingStrategy":"validation","validationCode":"// guard injection points: only access AuthzClient off the event loop\nif (Vertx.currentContext() != null && Vertx.currentContext().isEventLoopContext()) {\n    throw new IllegalStateException(\"Inject AuthzClient from a worker thread (@Blocking or Uni emitOn)\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    AuthzClient c = authzClient; // injected\n    c.protection().resource().findById(id);\n} catch (BlockingOperationNotAllowedException e) {\n    log.error(\"Move AuthzClient usage to a worker thread (@Blocking) or use static policy enforcers\", e);\n    throw e;\n}","preventionTips":["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."],"tags":["keycloak","vertx","event-loop","blocking"],"backgroundTag":"blocking-operation-not-allowed-on-io-thread","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}