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
- Move the config class from the deployment module (extensions/<name>/deployment) to the runtime module (extensions/<name>/runtime).
- Change the @ConfigRoot phase to BUILD_TIME only if the config genuinely is build-time-only and consumed solely at deployment.
- Update package names to the runtime module's package (io.quarkus.<ext>.runtime...) and fix imports/dependents.
- 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
- Runtime and RUN_TIME-fixed config classes always live in extensions/<name>/runtime.
- Deployment modules should only contain processors, build steps, and recorders.
- Follow the standard extension archetype layout when scaffolding new extensions.
- Review module placement in PRs that move config files between modules.
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
- Either @ConfigRoot or @ConfigMapping is missing on ${configR
- Unable to find javadoc for config item ${enclosingElement} $
- The class (${name}) cannot be created during deployment.
- Can not add converter ${converter.name()} that is not parame
- Converter ${converter.name()} must be parameterized with a s
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4b5d70d30ca8af07.
Report an issue: GitHub.