{"record":{"id":"a1d7cf8d61d94719","repo":"quarkusio/quarkus","slug":"oidc-tenants-s-and-s-share-the-same-resource","errorCode":null,"errorMessage":"OIDC tenants '%s' and '%s' share the same resource metadata path '%s', which is not supported","messagePattern":"OIDC tenants '(.+?)' and '(.+?)' share the same resource metadata path '(.+?)', which is not supported","errorType":"exception","errorClass":"io.quarkus.oidc.runtime.OIDCException","httpStatus":null,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/ResourceMetadataHandler.java","lineNumber":91,"sourceCode":"                }\n                String routePath = getResourceMetadataPath(configContext.oidcConfig(), resolver.getRootPath());\n                if (routePath.contains(\"*\")) {\n                    throw new IllegalStateException(\"Resource metadata path cannot contain a wildcard '*' character\");\n                }\n                OidcTenantConfig previousConfig = pathCache.put(routePath, configContext.oidcConfig());\n                if (previousConfig == null) {\n                    Handler<RoutingContext> routeHandler = new RouteHandler(configContext.oidcConfig(), resolver);\n                    builder.addPath(routePath, routeHandler);\n                } else {\n                    String previousTenantId = previousConfig.tenantId().get();\n                    String currentTenantId = configContext.oidcConfig().tenantId().get();\n                    // maybe invalid state, but technically it could happen that some produces a static tenant with\n                    // a same id as a dynamic tenant\n                    if (!previousTenantId.equals(currentTenantId)) {\n                        String errorMessage = \"OIDC tenants '%s' and '%s' share the same resource metadata path '%s', which is not supported\"\n                                .formatted(previousTenantId, currentTenantId, routePath);\n                        LOG.error(errorMessage);\n                        throw new OIDCException(errorMessage);\n                    }\n                }\n            }\n        }\n        if (builder != null) {\n            pathMatcher = builder.build();\n        } else {\n            pathMatcher = null;\n        }\n    }\n\n    static String getResourceMetadataPath(OidcTenantConfig oidcConfig, String configuredRootPath) {\n        String configuredResource = oidcConfig.resourceMetadata().resource().orElse(\"\");\n\n        String relativePath = null;\n\n        if (configuredResource.startsWith(HTTP_SCHEME)) {\n            relativePath = URI.create(configuredResource).getRawPath();","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/ResourceMetadataHandler.java#L73-L109","documentation":"ResourceMetadataHandler.createOrUpdatePathMatcher throws OIDCException when two different OIDC tenants resolve to the same resource metadata path. Each tenant must have a unique resource metadata route; a collision makes routing ambiguous, so startup/update fails with this error (logged and rethrown).","triggerScenarios":"During setup or updatePathMatcher, pathCache.put returns a previous config for the same routePath whose tenant id differs from the current one; e.g. two tenants (static or dynamic) configured with the same base path for resource metadata.","commonSituations":"Two tenants with identical or overlapping resource-metadata base paths; dynamic tenants created at runtime colliding with a static tenant's path; copy-paste config where the second tenant was not given a distinct base path.","solutions":["Give each tenant a unique resource metadata base path in its OidcTenantConfig","Fix the tenant id duplication/collision by renaming one of the tenants","Review TenantResolver-generated dynamic tenant configs so paths do not overlap static tenant paths"],"exampleFix":"// before\nquarkus.oidc.tenant-a.resource-metadata.base-path=/api\nquarkus.oidc.tenant-b.resource-metadata.base-path=/api\n// after\nquarkus.oidc.tenant-a.resource-metadata.base-path=/api/tenant-a\nquarkus.oidc.tenant-b.resource-metadata.base-path=/api/tenant-b","handlingStrategy":"validation","validationCode":"Set<String> seen = new HashSet<>();\nfor (OidcTenantConfig t : tenants) {\n    if (!seen.add(resourceMetadataPath(t))) throw new IllegalArgumentException(\"Duplicate resource metadata path across tenants\");\n}","typeGuard":null,"tryCatchPattern":"try { startApplication(); } catch (OIDCException e) { log.error(\"Two tenants share a resource metadata path: give each a unique base path\", e); }","preventionTips":["Plan unique base paths per tenant up front","Check dynamic TenantResolver outputs for path collisions against static tenants","Add a startup assertion that all tenant resource metadata paths are distinct"],"tags":["oidc","multi-tenant","resource-metadata","path-collision"],"backgroundTag":"tenant-path-collision","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}