quarkusio/quarkus · critical · IllegalStateException

Multiple interface io.quarkus.oidc.UserInfo beans registered

Error message

Multiple interface io.quarkus.oidc.UserInfo beans registered

What it means

verifyResolvers() reports ambiguity of the UserInfo cache: more than one bean implementing io.quarkus.oidc.UserInfo (used as a cache/provider abstraction for UserInfo endpoint results) is registered. Note the message text names UserInfo even though the checked field is userInfoCache; startup fails so OIDC can guarantee a single UserInfo source.

Source

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

        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
                    public Uni<OidcTenantConfig> apply(OidcTenantConfig oidcTenantConfig) {
                        if (oidcTenantConfig != null) {
                            return Uni.createFrom().item(oidcTenantConfig);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove or un-annotate all but one UserInfo bean/producer.
  2. Mark the desired bean @Alternative with @Priority so it is the only resolvable one.
  3. Review @Produces methods returning UserInfo and consolidate them into a single producer.

Example fix

// before
@Produces @ApplicationScoped UserInfo userInfoA() { ... }
@Produces @ApplicationScoped UserInfo userInfoB() { ... } // ambiguous

// after - only one producer remains
@Produces @ApplicationScoped UserInfo userInfoA() { ... }
Defensive patterns

Strategy: validation

Validate before calling

if (CDI.current().select(UserInfo.class).stream().count() > 1) {
    throw new IllegalStateException("Duplicate UserInfo producers detected");
}

Prevention

When it happens

Trigger: Register multiple beans whose type is UserInfo (or implementing the UserInfo cache abstraction injected as userInfoCache) so userInfoCache.isAmbiguous() is true in DefaultTenantConfigResolver's @PostConstruct.

Common situations: Customizing UserInfo handling while a default producer also exists; accidental duplicate producer methods (@Produces UserInfo) in different beans; copy-pasted CDI producers from another project.

Related errors


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