quarkusio/quarkus · critical · IllegalStateException

Multiple interface io.quarkus.oidc.runtime.TokenIntrospectio

Error message

Multiple interface io.quarkus.oidc.runtime.TokenIntrospectionCache beans registered

What it means

verifyResolvers() throws when the container finds multiple TokenIntrospectionCache beans. OIDC expects exactly one cache used to store introspected access-token results; ambiguity is detected eagerly at startup to prevent unpredictable caching.

Source

Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java:104

        this.securityEventObserved = SecurityEventHelper.isEventObserved(new SecurityEvent(null, (SecurityIdentity) null),
                beanManager, securityEventsEnabled);
        this.tenantConfigBean = tenantConfigBean;
        this.annotationBasedTenantResolutionEnabled = Boolean.getBoolean(OidcUtils.ANNOTATION_BASED_TENANT_RESOLUTION_ENABLED);
        this.rootPath = rootPath;
        this.staticTenantResolver = new StaticTenantResolver(tenantConfigBean, rootPath, resolveTenantsWithIssuer,
                tenantResolverInstance);
    }

    @PostConstruct
    public void verifyResolvers() {
        if (tenantConfigResolver.isResolvable() && tenantConfigResolver.isAmbiguous()) {
            throw new IllegalStateException("Multiple " + TenantConfigResolver.class + " beans registered");
        }
        if (tokenStateManager.isAmbiguous()) {
            throw new IllegalStateException("Multiple " + TokenStateManager.class + " beans registered");
        }
        if (tokenIntrospectionCache.isAmbiguous()) {
            throw new IllegalStateException("Multiple " + TokenIntrospectionCache.class + " beans registered");
        }
        if (userInfoCache.isAmbiguous()) {
            throw new IllegalStateException("Multiple " + UserInfo.class + " beans registered");
        }
        if (javaScriptRequestChecker.isAmbiguous()) {
            throw new IllegalStateException("Multiple " + JavaScriptRequestChecker.class + " beans registered");
        }

    }

    List<AuthenticationCompletionAction> authenticationCompletionActions() {
        return authenticationCompletionActions;
    }

    Uni<OidcTenantConfig> resolveConfig(RoutingContext context) {
        return getDynamicTenantConfig(context)
                .flatMap(new Function<OidcTenantConfig, Uni<? extends OidcTenantConfig>>() {
                    @Override

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep exactly one TokenIntrospectionCache bean; remove or un-annotate (e.g. @Vetoed / remove scope annotation) the other.
  2. Mark the intended implementation @Alternative with a higher @Priority so it unambiguously wins.
  3. Use quarkus.oidc.token-introspection-cache.* config to tune the built-in cache rather than adding a second one.

Example fix

// before
@ApplicationScoped class Cache1 implements TokenIntrospectionCache { ... }
@ApplicationScoped class Cache2 implements TokenIntrospectionCache { ... }

// after
@ApplicationScoped @Alternative @Priority(1)
class Cache2 implements TokenIntrospectionCache { ... }
// Cache1 no longer a bean (removed or @Vetoed)
Defensive patterns

Strategy: validation

Validate before calling

if (CDI.current().select(TokenIntrospectionCache.class).stream().count() > 1) {
    throw new IllegalStateException("Only one TokenIntrospectionCache may be registered");
}

Prevention

When it happens

Trigger: Two beans implementing io.quarkus.oidc.runtime.TokenIntrospectionCache are in the deployment, making the injected Instance ambiguous during @PostConstruct of DefaultTenantConfigResolver.

Common situations: Providing a custom TokenIntrospectionCache (e.g. Redis-backed) while another is registered; duplicate dependency bringing a second cache implementation; leaving both an old and a new cache class annotated.

Related errors


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