# Working With Config

> Quarkus configuration conventions: @ConfigMapping interfaces, config phases, and migration from legacy @ConfigRoot classes.

- Skill: `quarkusio/working-with-config` (Agent Skill)
- Install (CLI): `npx skillmds@latest add quarkusio/working-with-config`
- Raw SKILL.md: https://api.skillmd.com/api/skills/quarkusio/working-with-config/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: quarkusio (https://skillmd.com/u/quarkusio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/quarkusio/working-with-config

---


# 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)

```java
@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`:

```java
// 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.

```java
@ConfigMapping(prefix = "some.custom.prefix")
public interface MyCustomConfig {

    /** A non-root config property. */
    String someValue();
}
```

### Wrong (legacy approach, do not use)

```java
// 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 `@BuildStep`
  methods — **not injectable via `@Inject` in runtime CDI beans**.
- **`BUILD_AND_RUN_TIME_FIXED`** — Read at build time, also available at
  runtime. Cannot change without rebuilding. Available in both `@BuildStep`
  methods and runtime CDI beans.
- **`RUN_TIME`** — Read at application startup. Can change between runs.
  Available in runtime CDI beans via `@Inject`. **Not** available in
  `@BuildStep` methods.

## 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:

```xml
<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 `phase` explicitly on `@ConfigRoot` — the default is
  `BUILD_TIME`, which is not injectable via CDI at runtime
- Configuration is immutable — never mutate config objects
- Use `RUN_TIME` phase unless the config genuinely affects build-time behavior
- Do NOT access `RUN_TIME` config during build steps — it is not available yet
- `BUILD_TIME` config changes require a rebuild
- When adding new `quarkus.*` properties, always register them in a
  `@ConfigMapping` interface. Reading them programmatically via
  `ConfigProvider.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.

