# How to Use Variable Expansion with ${variable} Syntax in OWNER Property Values

> Learn to use variable expansion with ${variable} syntax in OWNER property values. Dynamically configure settings using environment variables system properties and custom maps in property files and Java annotations.

- Repository: [Matteo Baccan/owner](https://github.com/matteobaccan/owner)
- Tags: how-to-guide
- Published: 2026-03-07

---

**OWNER (matteobaccan/owner) supports variable expansion that substitutes `${variable}` placeholders with values from merged environment variables, system properties, and user-defined maps, enabling dynamic configuration in both property files and Java annotations.**

OWNER is a Java configuration mapping library that eliminates boilerplate when loading properties. Its **variable expansion** feature allows you to reference other configuration keys, system properties, or environment variables using the `${variable}` syntax directly inside `@DefaultValue` strings, `@Key` annotations, and external property files.

## How Variable Expansion Works in OWNER

According to the source code in [`owner/src/main/java/org/aeonbits/owner/VariablesExpander.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/VariablesExpander.java), the library constructs a merged lookup map before performing string substitution. When you call `ConfigFactory.create()`, the framework builds a `ConfigImpl` instance and initializes a `VariablesExpander` that combines three sources in priority order—first match wins:

1. **Environment variables** from `System.getenv()`
2. **System properties** from `System.getProperties()`
3. **User-supplied Properties** passed as arguments to `ConfigFactory.create()`

The expander creates a `StrSubstitutor` (implemented in [`owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java)) backed by this merged map. It calls `StrSubstitutor.replace()` on every string requiring expansion, including values in `@DefaultValue` annotations, `@Key` annotations (supported since Owner 1.0.6), and property files. The expander also handles tilde (`~`) expansion for user home directories via `Util.expandUserHome` before delegating to the substitutor.

## Configuring Property Files with ${variable} Syntax

You can nest references within `.properties` files to build composite values dynamically. The `${variable}` placeholders resolve against other properties defined in the same file or external sources.

```properties

# src/test/resources/example.properties

story=The ${animal} jumped over the ${target}
animal=quick ${color} fox
target=${target.attribute} dog
target.attribute=lazy
color=brown

```

When loaded through a mapped interface, the `${animal}` and `${target}` references resolve recursively, producing the final interpolated string.

## Using ${variable} in @DefaultValue Annotations

Define dynamic default values directly in your configuration interface using the `${variable}` syntax. The `VariablesExpander` processes these annotations when generating the configuration proxy.

```java
// src/test/java/org/aeonbits/owner/variableexpansion/StoryConfig.java
public interface StoryConfig extends Config {

    @DefaultValue("The ${animal} jumped over the ${target}")
    String story();

    @DefaultValue("quick ${color} fox")
    String animal();

    @DefaultValue("${target.attribute} dog")
    String target();

    @DefaultValue("lazy")
    @Key("target.attribute")
    String targetAttribute();

    @DefaultValue("brown")
    String color();
}

```

```java
StoryConfig cfg = ConfigFactory.create(StoryConfig.class);
System.out.println(cfg.story());   // → The quick brown fox jumped over the lazy dog

```

The expansion engine resolves `${target.attribute}` before substituting it into the `${target}` placeholder, demonstrating recursive variable resolution within the same configuration instance.

## Dynamic Key Names with @Key Variable Expansion

Since version 1.0.6, OWNER supports **variable expansion inside `@Key` annotations**, allowing runtime determination of which property key to look up. This enables environment-specific configuration structures without changing interface code.

```java
// src/test/java/org/aeonbits/owner/variableexpansion/KeyExpansionExample.java
@Sources("classpath:org/aeonbits/owner/variableexpansion/KeyExpansionExample.xml")
public interface ExpandsFromAnotherKey extends Config {

    @DefaultValue("dev")
    String env();

    @Key("servers.${env}.name")
    String name();

    @Key("servers.${env}.hostname")
    String hostname();

    @Key("servers.${env}.port")
    Integer port();

    @Key("servers.${env}.user")
    String user();

    @Key("servers.${env}.password")
    String password();
}

```

Inject runtime values by supplying a `Map` when creating the config instance:

```java
Map<String, String> vars = new HashMap<>();
vars.put("env", "uat");

ExpandsFromAnotherKey cfg = ConfigFactory.create(ExpandsFromAnotherKey.class, vars);
System.out.println(cfg.name());   // → User Acceptance Test

```

The `${env}` placeholder in each `@Key` resolves before the underlying XML source is queried, effectively switching the property namespace based on the injected variable.

## Injecting System Properties and Environment Variables

Reference system properties and environment variables directly using the `${variable}` syntax. The `VariablesExpander` automatically includes `System.getenv()` and `System.getProperties()` in its lookup chain.

```java
public interface SystemExample extends Config {

    @DefaultValue("Welcome, ${user.name}")
    String welcome();

    @DefaultValue("${TMPDIR}/tempFile.tmp")
    File tempFile();
}

```

```java
System.setProperty("user.name", "Alice");

SystemExample cfg = ConfigFactory.create(SystemExample.class,
                                        System.getProperties(),
                                        System.getenv());

System.out.println(cfg.welcome());   // → Welcome, Alice

```

Pass `System.getProperties()` and `System.getenv()` explicitly to `ConfigFactory.create()` to ensure the expander can resolve standard placeholders like `${user.name}`, `${java.io.tmpdir}`, or custom environment variables such as `${TMPDIR}`.

## Disabling Variable Expansion with @DisableFeature

If you need literal `${variable}` text without substitution, annotate the interface or specific method with `@DisableFeature(VARIABLE_EXPANSION)`. This prevents the `VariablesExpander` from processing the string.

```java
public interface NoExpansion extends Config {

    @DisableFeature(VARIABLE_EXPANSION)
    @DefaultValue("Hello ${world}.")
    String greet();          // returns the literal text
}

```

```java
NoExpansion cfg = ConfigFactory.create(NoExpansion.class);
System.out.println(cfg.greet());   // → Hello ${world}.

```

## Summary

- **Variable expansion** uses `${variable}` syntax and is handled by `VariablesExpander` using a merged map of environment variables, system properties, and user-supplied properties.
- Resolution follows a "first match wins" priority: environment variables override system properties, which override explicitly passed `Properties` objects.
- The feature works in `@DefaultValue` strings, property file values, and `@Key` annotations (since Owner 1.0.6), enabling both value interpolation and dynamic key selection.
- Disable expansion per-method or per-interface using `@DisableFeature(VARIABLE_EXPANSION)` when literal `${}` text is required.
- Runtime injection is achieved by passing a `Map<String, String>` to `ConfigFactory.create()`, allowing dynamic configuration without recompilation.

## Frequently Asked Questions

### Can I use ${variable} syntax in @Key annotations?

Yes, OWNER has supported **variable expansion in `@Key` annotations since version 1.0.6**. The `VariablesExpander` processes the `${variable}` placeholders in the annotation value before querying the underlying properties source, allowing dynamic key names like `servers.${env}.hostname`.

### What is the variable resolution priority in OWNER?

The resolution order follows a strict hierarchy implemented in [`VariablesExpander.java`](https://github.com/matteobaccan/owner/blob/main/VariablesExpander.java): first **environment variables** (`System.getenv()`), then **system properties** (`System.getProperties()`), and finally **user-supplied Properties** passed to `ConfigFactory.create()`. The first source containing the variable name wins.

### How do I disable variable expansion for specific configuration methods?

Annotate the method (or the entire interface) with `@DisableFeature(VARIABLE_EXPANSION)`. This instructs the `ConfigFactory` to skip the `VariablesExpander` step for that method, returning the raw string including literal `${variable}` text instead of attempting substitution.

### Can I inject runtime values for ${variable} placeholders?

Yes, provide a `Map<String, String>` or `Properties` object as an argument to `ConfigFactory.create(MyConfig.class, myMap)`. The map entries are merged into the variable lookup context with lower priority than system properties, allowing you to override or define placeholder values at runtime without modifying system environment variables.