quarkusio/quarkus · critical · IllegalStateException
Multiple interface io.quarkus.oidc.TenantConfigResolver bean
Error message
Multiple interface io.quarkus.oidc.TenantConfigResolver beans registered
What it means
DefaultTenantConfigResolver's @PostConstruct verifyResolvers() checks CDI injected Instances for ambiguity. If more than one bean implements io.quarkus.oidc.TenantConfigResolver and is resolvable, Quarkus cannot pick a single tenant resolver, so startup fails with IllegalStateException naming the interface.
Source
Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java:98
DefaultTenantConfigResolver(BlockingSecurityExecutor blockingExecutor, BeanManager beanManager,
Instance<TenantResolver> tenantResolverInstance,
@ConfigProperty(name = "quarkus.oidc.resolve-tenants-with-issuer") boolean resolveTenantsWithIssuer,
@ConfigProperty(name = "quarkus.security.events.enabled") boolean securityEventsEnabled,
@ConfigProperty(name = "quarkus.http.root-path") String rootPath, TenantConfigBean tenantConfigBean) {
this.blockingRequestContext = new BlockingTaskRunner<OidcTenantConfig>(blockingExecutor);
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;View on GitHub (pinned to e1c734241f)
Solutions
- Keep only one TenantConfigResolver bean; delete or merge the duplicate implementation.
- Mark one bean with @Alternative/@Priority or @Default (and the others with @Alternative) so only one is resolvable.
- If you intended to support multiple tenants, use static quarkus.oidc.<tenant>.* configuration or one resolver that dispatches by RoutingContext, not multiple resolvers.
Example fix
// before
class TenantAResolver implements TenantConfigResolver { ... }
class TenantBResolver implements TenantConfigResolver { ... }
// after - single resolver dispatching per request
class AppTenantResolver implements TenantConfigResolver {
public Uni<OidcTenantConfig> resolve(RoutingContext ctx) {
return ctx.request().host().startsWith("b.") ? tenantB() : tenantA();
}
} Defensive patterns
Strategy: validation
Validate before calling
// Before deploying: assert exactly one TenantConfigResolver bean
import jakarta.enterprise.inject.Instance;
Instance<TenantConfigResolver> resolvers = CDI.current().select(TenantConfigResolver.class);
if (resolvers.stream().count() > 1) {
throw new IllegalStateException("Register only one TenantConfigResolver");
} Prevention
- Search the codebase for 'implements TenantConfigResolver' before adding a new one
- Use @Alternative + @Priority when overriding a resolver
- Prefer one resolver dispatching per request over multiple resolver beans
- Run the app once locally before release; this fails at startup, not at runtime
When it happens
Trigger: Deploy an application with two or more @ApplicationScoped beans implementing TenantConfigResolver that are both eligible for injection into DefaultTenantConfigResolver (no @Default/@Alternative disambiguation), then start the app; the PostConstruct on DefaultTenantConfigResolver throws.
Common situations: Adding a second custom TenantConfigResolver for multi-tenant OIDC while another one (or a library-provided one) already exists; copying example code that defines a resolver while keeping an old one; version upgrades bringing in an extra resolver bean.
Related errors
- Multiple interface io.quarkus.oidc.runtime.TokenStateManager
- Multiple interface io.quarkus.oidc.runtime.TokenIntrospectio
- Multiple interface io.quarkus.oidc.UserInfo beans registered
- Multiple interface io.quarkus.oidc.JavaScriptRequestChecker
- Multiple handler classes, either specify the quarkus.lambda.
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/82acba25fe410b7a.
Report an issue: GitHub.