{"record":{"id":"2fd3ec9e228a39d1","repo":"quarkusio/quarkus","slug":"either-configroot-or-configmapping-is-missing-on","errorCode":null,"errorMessage":"Either @ConfigRoot or @ConfigMapping is missing on ${configRoot}","messagePattern":"Either @ConfigRoot or @ConfigMapping is missing on (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java","lineNumber":74,"sourceCode":"                configRootAnnotation = annotationMirror;\n                continue;\n            }\n            if (annotationName.equals(Types.ANNOTATION_CONFIG_MAPPING)) {\n                configMappingAnnotion = annotationMirror;\n                continue;\n            }\n            if (annotationName.equals(Types.ANNOTATION_CONFIG_DOC_PREFIX)) {\n                configDocPrefixAnnotation = annotationMirror;\n                continue;\n            }\n            if (annotationName.equals(Types.ANNOTATION_CONFIG_DOC_FILE_NAME)) {\n                configDocFileNameAnnotation = annotationMirror;\n                continue;\n            }\n        }\n\n        if (configRootAnnotation == null || configMappingAnnotion == null) {\n            throw new IllegalStateException(\"Either @ConfigRoot or @ConfigMapping is missing on \" + configRoot);\n        }\n\n        final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = configRootAnnotation\n                .getElementValues();\n        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {\n            if (\"phase()\".equals(entry.getKey().toString())) {\n                configPhase = ConfigPhase.valueOf(entry.getValue().getValue().toString());\n            }\n        }\n\n        validateRuntimeConfigOnDeploymentModules(configPhase, configRoot);\n\n        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : configMappingAnnotion.getElementValues()\n                .entrySet()) {\n            if (\"prefix()\".equals(entry.getKey().toString())) {\n                prefix = entry.getValue().getValue().toString();\n            }\n        }","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java#L56-L92","documentation":"This IllegalStateException is thrown by the Quarkus annotation processor while scanning @ConfigRoot-annotated classes during config documentation generation. Every config root class must carry both the Quarkus @ConfigRoot (or legacy @ConfigRoot) annotation and MicroProfile @ConfigMapping; if either annotation mirror cannot be resolved on the element, processing aborts. It indicates a malformed or inconsistent configuration class definition in an extension.","triggerScenarios":"Compiling an extension whose config class is annotated with @ConfigMapping but lacks @ConfigRoot, or vice versa, or when the annotation is present via inheritance/indirection such that the annotation processor cannot find its mirror on the TypeElement passed to onConfigRoot.","commonSituations":"Adding a new @ConfigMapping interface to an extension and forgetting the Quarkus @ConfigRoot annotation; upgrading Quarkus and migrating off legacy config classes; copy-pasting a config class and dropping one annotation.","solutions":["Add the missing annotation: annotate the class with both @ConfigMapping and @ConfigRoot (with phase, e.g. @ConfigRoot(phase = ConfigPhase.BUILD_TIME)).","If the class is legacy-style, either add @ConfigMapping or convert it fully to the @ConfigMapping model per current Quarkus conventions.","Verify the annotations are on the same TypeElement the processor scans (not a superclass) and are runtime-retained source-visible as required.","Rebuild the extension module with ./mvnw install to re-run annotation processing and confirm the fix."],"exampleFix":"// before\n@ConfigMapping(prefix = \"quarkus.my-ext\")\npublic interface MyExtConfig { }\n// after\n@ConfigRoot(phase = ConfigPhase.BUILD_TIME)\n@ConfigMapping(prefix = \"quarkus.my-ext\")\npublic interface MyExtConfig { }","handlingStrategy":"validation","validationCode":"if (!typeElement.getAnnotationMirrors().stream()\n        .map(a -> a.getAnnotationType().toString())\n        .anyMatch(n -> n.endsWith(\"ConfigRoot\"))) {\n    throw new IllegalStateException(typeElement + \" must be annotated with @ConfigRoot\");\n}","typeGuard":"boolean hasAnnotation(TypeElement e, String simpleName) {\n    return e.getAnnotationMirrors().stream()\n        .anyMatch(a -> a.getAnnotationType().asElement().getSimpleName()\n            .contentEquals(simpleName));\n}","tryCatchPattern":"try {\n    compile(extensionModule);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"@ConfigRoot or @ConfigMapping is missing\")) {\n        fixConfigAnnotations(e);\n    } else throw e;\n}","preventionTips":["Always pair @ConfigMapping with @ConfigRoot on Quarkus config classes.","Copy config class templates from existing extensions rather than writing from scratch.","Run the extension build locally before pushing so annotation processing errors surface early.","When migrating off legacy config classes, update both annotations in the same commit."],"tags":["quarkus","annotation-processor","config","build-time"],"backgroundTag":"missing-config-annotation","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"}