Config Profiles & Relaxed Binding
Starting in 10.0.0, Juneau's config system supports two cloud-native ergonomics borrowed from the Spring Boot
world: environment profiles (<name>-<profile> config overlays) and relaxed binding (one logical key
resolvable from any common environment-variable spelling). Both are opt-in-friendly and change no existing behavior
by default.
Config profiles
A profile layers an environment-specific overlay (dev / stage / prod, etc.) on top of a base config. Given a
base config my.cfg and an active profile stage, the overlay file my-stage.cfg is merged over the base:
- profile wins over base for any section/key the profile redefines;
- base-only keys survive unchanged;
- when multiple profiles are active, the last one wins for shared keys (earlier profiles still contribute their unique keys).
YAML bases work identically with my-stage.yml overlays.
Activation
Set the active profiles with the juneau.profiles.active key — a comma-separated list — via a system property, an
environment variable, or a config key:
java -Djuneau.profiles.active=stage,cloud -jar myapp.jar
# or
export JUNEAU_PROFILES_ACTIVE=stage,cloud # relaxed binding maps this to juneau.profiles.active
Or programmatically on the config builder:
var config = Config.create()
.name("my.cfg")
.profiles("stage", "cloud") // base my.cfg + my-stage.cfg + my-cloud.cfg (cloud wins)
.build();
With no active profiles, the base config is used verbatim — existing single-file configs are completely unaffected.
How the overlay works
Profile resolution happens at the store layer: when profiles are active, the configured ConfigStore is wrapped
so that a read of the base name returns the base contents with each active profile's overlay merged on top
(ProfileConfigStore + the ProfileMerge engine). Because the merged result flows through the normal config parse,
variable resolution, format handling, and live reload all behave exactly as for a single-file config.
Live reload
Profile overlays participate in live reload: a change to the base file or to any active profile overlay file
re-runs the merge and fires the usual ConfigEventListener change events, so a running application picks up
profile-file edits without a restart.
Spring Boot piggyback
When the Spring Boot bridge (juneau-rest-server-springboot) is present, juneau.profiles.active piggybacks on
Spring's active profiles — Environment.getActiveProfiles() becomes the source of truth, so spring.profiles.active
(however it was set: property, env, SpringApplication.setAdditionalProfiles, …) drives Juneau config-profile
selection with no separate configuration. An explicit juneau.profiles.active property still wins if you set one.
Relaxed binding
Relaxed binding lets a single logical configuration key be supplied in any of the common environment conventions and still resolve. This is what makes Kubernetes-style environment variables map cleanly onto config keys:
| Logical key | Also resolvable from |
|---|---|
my.prop | MY_PROP |
myProp | MY_PROP, my.prop |
MY_PROP | my.prop |
MySection/myKey | MY_SECTION_MY_KEY |
my-prop | MY_PROP, my.prop |
By default the system-environment source in the Settings singleton is wrapped with relaxed binding
(Settings.RELAXED_SYSTEM_ENV_SOURCE), so this works out of the box for env-var lookups.
How candidates are generated
A lookup is always tried verbatim first, so exact-match behavior is preserved and an already-canonical key resolves with no transformation. Only on a miss are relaxed variants tried, in order:
- verbatim — the requested name, unchanged;
- UPPER_UNDERSCORE — every run of non-alphanumeric characters, and every lower→upper camel-case boundary, becomes
a single
_, then the whole name is upper-cased (the dominant env-var form); - lower.dotted — the same separator collapse joined with
.and lower-cased.
The decorator works in the lookup-key → candidate-name direction only (the well-defined direction): you ask for a
known logical key and it generates the environment-convention spellings to probe. It never tries to reverse a flat
environment name (where the section/key boundary is ambiguous) back into a section/key pair.
Applying it elsewhere
To add relaxed binding over any other PropertySource, wrap it:
import org.apache.juneau.commons.settings.*;
var settings = Settings.create()
.addSource(new RelaxedPropertySource(mySource))
.build();