quarkusio/quarkus · error · IllegalStateException

Error on %s: Configuration classes with ConfigPhase.RUN_TIME

Error message

Error on %s: Configuration classes with ConfigPhase.RUN_TIME or ConfigPhase.BUILD_AND_RUNTIME_FIXED phases, must reside in the respective module.

What it means

The Quarkus annotation processor enforces that configuration classes declared with ConfigPhase.RUN_TIME or ConfigPhase.BUILD_AND_RUN_TIME_FIXED live in the runtime module, not the deployment module. Runtime-phase config is needed by application runtime code, and deployment-only artifacts must not contain it. When such a config root is found in a module whose ExtensionModule type is DEPLOYMENT, validation throws this IllegalStateException.

Source

Thrown at core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java:224

            AnnotationMirror configDocEnumValue = annotations.get(Types.ANNOTATION_CONFIG_DOC_ENUM_VALUE);
            if (configDocEnumValue != null) {
                Map<String, Object> enumValueValues = utils.element().getAnnotationValues(configDocEnumValue);
                explicitValue = (String) enumValueValues.get("value");
            }

            enumConstants.put(enumElement.getSimpleName().toString(), new EnumConstant(explicitValue));
        }

        EnumDefinition enumDefinition = new EnumDefinition(enumTypeElement.getQualifiedName().toString(),
                enumConstants);
        configCollector.addResolvedEnum(enumDefinition);
    }

    private void validateRuntimeConfigOnDeploymentModules(ConfigPhase configPhase, TypeElement configRoot) {
        if (configPhase.equals(ConfigPhase.RUN_TIME) || configPhase.equals(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) {
            ExtensionModule.ExtensionModuleType type = config.getExtensionModule().type();
            if (type.equals(ExtensionModule.ExtensionModuleType.DEPLOYMENT)) {
                throw new IllegalStateException(String.format(
                        "Error on %s: Configuration classes with ConfigPhase.RUN_TIME or " +
                                "ConfigPhase.BUILD_AND_RUNTIME_FIXED phases, must reside in the respective module.",
                        configRoot.getSimpleName().toString()));
            }
        }
    }

    private void handleCommonPropertyAnnotations(DiscoveryConfigProperty.Builder builder,
            Map<String, AnnotationMirror> propertyAnnotations, ResolvedType resolvedType, String sourceElementName) {

        AnnotationMirror deprecatedAnnotation = propertyAnnotations.get(Deprecated.class.getName());
        if (deprecatedAnnotation != null) {
            String since = (String) utils.element().getAnnotationValues(deprecatedAnnotation).get("since");
            // TODO add more information about the deprecation, typically the reason and a replacement
            builder.deprecated(since, null, null);
        }

        AnnotationMirror configDocSectionAnnotation = propertyAnnotations.get(Types.ANNOTATION_CONFIG_DOC_SECTION);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Move the config class from the deployment module (extensions/<name>/deployment) to the runtime module (extensions/<name>/runtime).
  2. Change the @ConfigRoot phase to BUILD_TIME only if the config genuinely is build-time-only and consumed solely at deployment.
  3. Update package names to the runtime module's package (io.quarkus.<ext>.runtime...) and fix imports/dependents.
  4. Rebuild the extension with ./mvnw install -f extensions/<name>/ to re-run validation.

Example fix

// before (deployment module)
// extensions/foo/deployment/src/main/java/io/quarkus/foo/deployment/FooConfig.java
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
@ConfigMapping(prefix = "quarkus.foo")
public interface FooConfig { }
// after (runtime module)
// extensions/foo/runtime/src/main/java/io/quarkus/foo/runtime/FooConfig.java
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
@ConfigMapping(prefix = "quarkus.foo")
public interface FooConfig { }
Defensive patterns

Strategy: validation

Validate before calling

Path src = deploymentModule.resolve("src/main/java");
try (Stream<Path> s = Files.walk(src)) {
    List<Path> bad = s.filter(p -> {
        try {
            return Files.readString(p).contains("ConfigPhase.RUN_TIME");
        } catch (IOException e) { return false; }
    }).toList();
    if (!bad.isEmpty()) throw new IllegalStateException("Runtime config in deployment module: " + bad);
}

Type guard

boolean isRuntimeConfigInWrongModule(TypeElement configRoot, ExtensionModule module) {
    return (configRoot.getAnnotation(ConfigRoot.class) != null)
        && module.type() == ExtensionModule.ExtensionModuleType.DEPLOYMENT;
}

Try / catch

try {
    buildExtension();
} catch (IllegalStateException e) {
    if (e.getMessage().contains("must reside in the respective module")) {
        moveConfigClassToRuntimeModule();
    } else throw e;
}

Prevention

When it happens

Trigger: Compiling a deployment (quarkus-<ext>-deployment) module that contains a class annotated @ConfigRoot(phase = ConfigPhase.RUN_TIME) or ConfigPhase.BUILD_AND_RUN_TIME_FIXED, detected by validateRuntimeConfigOnDeploymentModules during config scanning.

Common situations: Creating a new runtime config interface but placing the file in the deployment module by accident; moving classes between modules during a refactor; generating config classes into the wrong source root.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/4b5d70d30ca8af07. Report an issue: GitHub.