Skip to main content

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 keyAlso resolvable from
my.propMY_PROP
myPropMY_PROP, my.prop
MY_PROPmy.prop
MySection/myKeyMY_SECTION_MY_KEY
my-propMY_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:

  1. verbatim — the requested name, unchanged;
  2. 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);
  3. 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();

See also​