This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new a382d57ab5 Add config profiles and relaxed binding to the config system
a382d57ab5 is described below
commit a382d57ab50568aee7d4c69501d3a19508cace7e
Author: James Bognar <[email protected]>
AuthorDate: Thu Jun 18 13:47:18 2026 -0400
Add config profiles and relaxed binding to the config system
---
pages/release-notes/10.0.0.md | 22 ++++
.../05.16.ConfigProfilesAndRelaxedBinding.md | 111 +++++++++++++++++++++
sidebars.ts | 5 +
3 files changed, 138 insertions(+)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index b7041586fd..f9aaa597ea 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -78,6 +78,28 @@ Key behaviours:
See the [View-Based Projection](/docs/topics/ViewProjection) topic page for
full documentation and a
Jackson migration table.
+### juneau-config / juneau-commons
+
+#### Config profiles + relaxed binding (cloud-native environment config)
+
+Juneau 10.0 adds two Spring-Boot-style ergonomics to the config + settings
system, both additive and off-by-default
+so existing configs are unaffected:
+
+- **Config profiles** — a `<name>-<profile>` overlay (e.g. `my-stage.cfg`, or
`my-stage.yml` for YAML bases) is merged
+ over the base config when a profile is active. Profile entries win over
base, base-only keys survive, and for multiple
+ active profiles the **last one wins**. Activated via
`Config.Builder.profiles(...)` or the `juneau.profiles.active`
+ key (system property / env var / config). Resolution happens at the store
layer (`ProfileConfigStore` + `ProfileMerge`),
+ so variable resolution, format handling, and **live reload** (a change to
the base *or* any active profile file
+ re-fires change listeners) all behave exactly as for a single-file config.
When the Spring Boot bridge is present,
+ `juneau.profiles.active` **piggybacks on `Environment.getActiveProfiles()`**
for a single source of truth.
+- **Relaxed binding** — a single logical config key resolves from any common
environment spelling, so k8s-style
+ env vars map cleanly: `my.prop` / `myProp` / `MySection/myKey` are satisfied
by `MY_PROP` / `MY_SECTION_MY_KEY`. The
+ default `Settings` env source is wrapped with `RelaxedPropertySource`
(verbatim-first, so exact-match is preserved);
+ it can wrap any other `PropertySource` too.
+
+See the new [Config Profiles & Relaxed
Binding](/docs/topics/ConfigProfilesAndRelaxedBinding) topic page for the
+activation conventions, overlay/merge precedence, and the candidate-generation
rules.
+
### juneau-microservice-jetty
#### `JettyMicroservice` zero-config facade + bundled defaults
diff --git a/pages/topics/05.16.ConfigProfilesAndRelaxedBinding.md
b/pages/topics/05.16.ConfigProfilesAndRelaxedBinding.md
new file mode 100644
index 0000000000..6d07ed6b57
--- /dev/null
+++ b/pages/topics/05.16.ConfigProfilesAndRelaxedBinding.md
@@ -0,0 +1,111 @@
+---
+title: "Config Profiles & Relaxed Binding"
+slug: ConfigProfilesAndRelaxedBinding
+---
+
+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:
+
+```bash
+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:
+
+```java
+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:
+
+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:
+
+```java
+import org.apache.juneau.commons.settings.*;
+
+var settings = Settings.create()
+ .addSource(new RelaxedPropertySource(mySource))
+ .build();
+```
+
+## See also
+
+- [Config Files](/docs/topics/ConfigFiles)
+- [Property Sources](/docs/topics/PropertySources)
+- [juneau-commons Settings](/docs/topics/JuneauCommonsSettings)
diff --git a/sidebars.ts b/sidebars.ts
index 7c70682de5..18eec85119 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1099,6 +1099,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/05.15.YamlConfigFiles',
label: '5.15. YAML
Config Files',
},
+ {
+ type: 'doc',
+ id:
'topics/05.16.ConfigProfilesAndRelaxedBinding',
+ label: '5.16. Config
Profiles & Relaxed Binding',
+ },
],
},
{