{"record":{"id":"6ef4e5586a4bbff0","repo":"quarkusio/quarkus","slug":"two-config-roots-with-different-extensions-or-pref","errorCode":null,"errorMessage":"Two config roots with different extensions or prefixes cannot be merged in the same specific config file: ${configRoot.getOverriddenDocFileName()}","messagePattern":"Two config roots with different extensions or prefixes cannot be merged in the same specific config file: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java","lineNumber":94,"sourceCode":"            try (InputStream resolvedModelIs = Files.newInputStream(resolvedModelPath)) {\n                ResolvedModel resolvedModel = JacksonMappers.yamlObjectReader().forType(ResolvedModel.class)\n                        .readValue(resolvedModelIs);\n\n                if (resolvedModel.getConfigRoots() == null || resolvedModel.getConfigRoots().isEmpty()) {\n                    continue;\n                }\n\n                for (ConfigRoot configRoot : resolvedModel.getConfigRoots()) {\n                    if (configRoot.getOverriddenDocFileName() != null) {\n                        ConfigRoot existingConfigRootInSpecificFile = configRootsInSpecificFile\n                                .get(configRoot.getOverriddenDocFileName());\n\n                        if (existingConfigRootInSpecificFile == null) {\n                            configRootsInSpecificFile.put(configRoot.getOverriddenDocFileName(), configRoot);\n                        } else {\n                            if (!existingConfigRootInSpecificFile.getExtension().equals(configRoot.getExtension())\n                                    || !existingConfigRootInSpecificFile.getPrefix().equals(configRoot.getPrefix())) {\n                                throw new IllegalStateException(\n                                        \"Two config roots with different extensions or prefixes cannot be merged in the same specific config file: \"\n                                                + configRoot.getOverriddenDocFileName());\n                            }\n\n                            existingConfigRootInSpecificFile.merge(configRoot);\n                        }\n\n                        continue;\n                    }\n\n                    Map<ConfigRootKey, ConfigRoot> extensionConfigRoots = configRoots.computeIfAbsent(\n                            normalizeExtension(configRoot.getExtension(), mergeCommonOrInternalExtensions),\n                            e -> new TreeMap<>());\n\n                    ConfigRootKey configRootKey = getConfigRootKey(javadocRepository, configRoot);\n                    ConfigRoot existingConfigRoot = extensionConfigRoots.get(configRootKey);\n\n                    if (existingConfigRoot == null) {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java#L76-L112","documentation":"ModelMerger.mergeModel merges config roots that are documented into the same 'specific' config file. Two config roots may share a doc filename only if they have the same extension and prefix; otherwise the merge is ambiguous and throws IllegalStateException naming the overridden doc file name.","triggerScenarios":"Two @ConfigMapping/@ConfigRoot classes resolve to the same overriddenDocFileName (via @ConfigDocFileName or filename derivation) but declare different extensions or prefixes, so mergeModel encounters a conflict while building the doc model.","commonSituations":"Two extensions (or an extension and an app) accidentally defining the same config doc file name with different quarkus.* prefixes; copy-pasting a config class and changing the prefix but not the doc file name; re-baselining extension config across modules.","solutions":["Give one of the conflicting config roots a distinct doc file name (change its overridden doc file name / @ConfigDocFileName)","Make the extensions/prefixes match if the roots genuinely belong together so they merge instead of conflict","Rename one config root's prefix if it was mistakenly duplicated","Identify the two roots targeting that file and refactor documentation grouping"],"exampleFix":"// before\n@ConfigMapping(prefix = \"ext-a\")\n@ConfigDoc(fileName = \"shared.adoc\")\ninterface AConfig {}\n@ConfigMapping(prefix = \"ext-b\")\n@ConfigDoc(fileName = \"shared.adoc\")\ninterface BConfig {}\n// after\n@ConfigMapping(prefix = \"ext-b\")\n@ConfigDoc(fileName = \"ext-b.adoc\")\ninterface BConfig {}","handlingStrategy":"validation","validationCode":"Map<String, DiscoveredConfigRoot> byFile = new HashMap<>();\nfor (DiscoveredConfigRoot root : roots) {\n    DiscoveredConfigRoot prev = byFile.putIfAbsent(root.getOverriddenDocFileName(), root);\n    if (prev != null && (!prev.getExtension().equals(root.getExtension()) || !prev.getPrefix().equals(root.getPrefix()))) {\n        throw new IllegalStateException(\"Conflicting roots share doc file: \" + root.getOverriddenDocFileName());\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return modelMerger.mergeModel(roots);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Two config roots\")) {\n        log.error(\"Assign distinct doc file names: \" + e.getMessage());\n    }\n    throw e;\n}","preventionTips":["Ensure each config root has a unique doc file name unless extensions and prefixes match","Don't copy config classes without updating prefix and doc file name","Run doc generation across all extension modules in CI to catch conflicts early"],"tags":["annotation-processor","config","documentation","conflict"],"backgroundTag":"config-doc-merge-conflict","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}