name: working-with-config description: > Quarkus configuration conventions: @ConfigMapping interfaces, config phases, and migration from legacy @ConfigRoot classes.
Working with Configuration
Use @ConfigMapping Interfaces (Not Legacy Classes)
Quarkus configuration must use @ConfigMapping interfaces. The legacy
@ConfigRoot-annotated classes with @ConfigItem have been fully removed
(see adr/0008-phasing-out-config-classes.adoc).
Correct (current approach)
@ConfigMapping(prefix = "quarkus.my-extension")
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
public interface MyExtensionRuntimeConfig {
/** Description of the property. */
@WithDefault("default-value")
String myProperty();
/** Optional property (absent means not set by the user). */
Optional<String> optionalProperty();
}
Do NOT Use Optional to Define Default Values
Never use Optional and provide a default via .orElse(...) in application
code. The default must be declared in the config mapping itself via
@WithDefault:
// BAD — do not do this
String name = config.optName().orElse("theDefaultName");
// GOOD — declare the default in the config mapping
@WithDefault("theDefaultName")
String name();
Non-root @ConfigMapping (without @ConfigRoot)
@ConfigMapping can also be used without @ConfigRoot for non-root
mappings. These are not auto-discovered — you must register them explicitly
(e.g., via @Inject in a CDI bean or programmatically with SmallRye Config).
Use this when mapping a sub-tree of configuration that does not represent an
extension's top-level config root.
@ConfigMapping(prefix = "some.custom.prefix")
public interface MyCustomConfig {
/** A non-root config property. */
String someValue();
}
Wrong (legacy approach, do not use)
// DO NOT use this pattern
@ConfigRoot(name = "my-extension", phase = ConfigPhase.RUN_TIME)
public class MyExtensionConfig {
@ConfigItem(defaultValue = "default-value")
String myProperty;
}
Config Phases
@ConfigRoot defaults to BUILD_TIME when no phase is specified. Always set
the phase explicitly — omitting it for a config injected at runtime will cause
UnsatisfiedResolutionException because BUILD_TIME configs are not registered
as CDI beans.
BUILD_TIME— Baked into the binary at build time, immutable at runtime. Use for settings that affect code generation. Only available in@BuildStepmethods — not injectable via@Injectin runtime CDI beans.BUILD_AND_RUN_TIME_FIXED— Read at build time, also available at runtime. Cannot change without rebuilding. Available in both@BuildStepmethods and runtime CDI beans.RUN_TIME— Read at application startup. Can change between runs. Available in runtime CDI beans via@Inject. Not available in@BuildStepmethods.
Naming
- Config interfaces:
<Feature>Config.java, or split as<Feature>BuildTimeConfig.java/<Feature>RuntimeConfig.java - Config prefix:
quarkus.<extension-name>
Annotation Processor (quarkus-extension-processor)
The quarkus-extension-processor annotation processor is required for all
extension modules, not just those with @ConfigRoot. It generates metadata
files (including META-INF/quarkus-config-roots.list for config discovery)
that Quarkus needs to wire extensions correctly. Without it, most extension
functionality will silently fail.
Both runtime/ and deployment/ POMs must include:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
Most modules already have this. If you create a new module or notice missing
metadata, verify the annotation processor is present in its pom.xml.
Key Rules
- Always specify
phaseexplicitly on@ConfigRoot— the default isBUILD_TIME, which is not injectable via CDI at runtime - Configuration is immutable — never mutate config objects
- Use
RUN_TIMEphase unless the config genuinely affects build-time behavior - Do NOT access
RUN_TIMEconfig during build steps — it is not available yet BUILD_TIMEconfig changes require a rebuild- When adding new
quarkus.*properties, always register them in a@ConfigMappinginterface. Reading them programmatically viaConfigProvider.getConfig().getOptionalValue(...)is fine, but the corresponding key must still be defined in a@ConfigMapping— otherwise SmallRye Config will emit "unknown property" warnings at startup.