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
- Remove or un-annotate all but one UserInfo bean/producer.
- Mark the desired bean @Alternative with @Priority so it is the only resolvable one.
- 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
- Grep for '@Produces' methods returning UserInfo and consolidate them
- Avoid copy-pasting CDI producers from other projects
- Document the single UserInfo producer as the app's source of truth
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
- Multiple interface io.quarkus.oidc.TenantConfigResolver bean
- Multiple interface io.quarkus.oidc.runtime.TokenStateManager
- Multiple interface io.quarkus.oidc.runtime.TokenIntrospectio
- Multiple interface io.quarkus.oidc.JavaScriptRequestChecker
- UserInfo is not required but UserInfo is expected to be the
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4aac1d14702fb9ea.
Report an issue: GitHub.