1
0

Three new article modules: configuration binding, profiles and config data, Spring AOP

configuration-properties/  @ConfigurationProperties vs @Value on Spring Boot 4.1.1.
  The relaxed-binding matrix is generated by binding each spelling rather than
  transcribed, and re-checked against real processes -- the in-process probe was
  wrong twice before it was right. Records the three findings that came out of it:
  @Value does get relaxed resolution inside Spring Boot (Boot attaches
  ConfigurationPropertySources), the configuration processor silently stops
  generating metadata on JDK 23+ when declared as a plain dependency, and @Valid is
  not what makes nested constraints run.

profiles-and-config/       Precedence, profiles, spring.config.import and config trees.
  /precedence reports every source holding a property in rank order with file and
  line, which turns "my profile file had no effect" into a two-line answer. Also
  pins the counterintuitive one: an imported file outranks the file that imported it.

spring-aop/                Designators, proxy types, and aspects that do not fire.
  One advice per supported designator so the reference table is generated from real
  matches; all fourteen unsupported designators fed to the parser. Two corrections to
  the reference documentation: unsupported designators throw
  UnsupportedPointcutPrimitiveException (extends RuntimeException, not
  IllegalArgumentException), and spring-boot-starter-aop was renamed to
  spring-boot-starter-aspectj in Boot 4.

19 contract tests across the three modules, 15 captured transcripts, all regenerated
by scripts/run-all.sh. Verified on Spring Boot 4.1.1, Spring Framework 7.0.9,
JDK 25.0.4.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gip4srpzMwjgoba6uEfbr5
This commit is contained in:
2026-09-08 16:36:17 +00:00
parent 958b401f0f
commit 86246dc860
107 changed files with 5075 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
[Index](../README.md) &middot; [Profiles &rarr;](02-profiles.md)
# 1. The precedence list
Spring Boot's documented order, lowest precedence first. Later entries win.
| # | Source |
|---|---|
| 1 | Default properties (`SpringApplication.setDefaultProperties`) |
| 2 | `@PropertySource` on `@Configuration` classes |
| 3 | **Config data**`application.properties`, `application.yaml`, profile-specific files, `spring.config.import` |
| 4 | `RandomValuePropertySource` (`random.*`) |
| 5 | **OS environment variables** |
| 6 | Java system properties (`-D`) |
| 7 | JNDI attributes from `java:comp/env` |
| 8 | `ServletContext` init parameters |
| 9 | `ServletConfig` init parameters |
| 10 | `SPRING_APPLICATION_JSON` |
| 11 | Command-line arguments |
| 12 | `properties` on `@SpringBootTest` |
| 13 | `@DynamicPropertySource` |
| 14 | `@TestPropertySource` |
| 15 | Devtools global settings |
## The two rows that matter
**Item 3 covers every file you write.** `application.yaml`, `application-prod.yaml`, an
imported config tree, a mounted ConfigMap — all of it is config data, all of it at rank 3.
**Item 5 is above it.** Every environment variable outranks every file.
Profile-specific files beat non-profile files, and later imports beat earlier ones, but those
are orderings *within* item 3. Nothing inside item 3 can reach item 5.
That single fact explains the bug this project exists for, and
[chapter 4](04-why-your-profile-file-lost.md) walks through it with a transcript.
## Seeing it for real
`/sources` prints the live stack, which is more useful than the table because it shows exactly
which files were loaded:
```
3. SimpleCommandLinePropertySource commandLineArgs
6. PropertiesPropertySource systemProperties
7. OriginAwareSystemEnvironmentPropertySource systemEnvironment
9. OriginTrackedMapPropertySource application-prod-metrics.yaml
10. OriginTrackedMapPropertySource application-prod-db.yaml
11. OriginTrackedMapPropertySource application-prod.yaml
12. OriginTrackedMapPropertySource application.yaml
```
Note rank 2 in the real stack, which the table does not mention:
`ConfigurationPropertySourcesPropertySource`, named `configurationProperties`. That is the
source Spring Boot attaches to give `${...}` placeholders the binder's relaxed name matching —
see the [binding project's chapter 2](../../configuration-properties/docs/02-relaxed-binding.md).

View File

@@ -0,0 +1,70 @@
[&larr; Precedence list](01-the-precedence-list.md) &middot; [Index](../README.md) &middot; [Seeing precedence &rarr;](03-seeing-precedence.md)
# 2. Profiles: files, documents and groups
## Profile-specific files
`application-<profile>.yaml`, loaded from the same locations as `application.yaml`, and always
overriding it. With several profiles active, last one wins:
`--spring.profiles.active=prod,live` means `application-live.yaml` beats
`application-prod.yaml`.
## Multi-document files
The same effect without multiplying files. Documents are separated by `---` and activated by
condition:
```yaml
demo:
greeting: from-multidoc-default-document
---
spring:
config:
activate:
on-profile: staging
demo:
greeting: from-multidoc-staging-document
```
Later documents win over earlier ones, so an unconditional first document acts as the default
and each conditional document overrides it. Measured in
[`04-import-and-multidoc.txt`](output/04-import-and-multidoc.txt).
`spring.config.activate.on-cloud-platform` and `spring.config.activate.on-profile` can be
combined; both must match.
## Profile groups
One profile that activates several:
```yaml
spring:
profiles:
group:
prod: prod-db,prod-metrics
```
`--spring.profiles.active=prod` reports all three as active, and all three
`application-<name>.yaml` files are loaded. Groups are resolved before config data is
processed, which is why declaring a group in `application.yaml` can still affect which files
get loaded.
## The activation Spring Boot refuses
`spring.profiles.active` cannot be set from a document that is itself profile-specific:
```
InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location
'class path resource [application-badactivation.yaml]' is invalid in a profile specific
resource [origin: ... - 12:13]
```
A profile that activates itself would change which files are loaded after the set of files had
already been decided. Boot refuses rather than half-applying it. `spring.profiles.include` has
the same restriction; `spring.config.activate.on-profile` is how you express the condition.
## `@Profile` is a different mechanism
`@Profile("prod")` on a bean is evaluated when the context is built, long after config data is
resolved. It decides which *beans* exist, not which *properties* are set. The two use the same
profile names and nothing else.

View File

@@ -0,0 +1,59 @@
[&larr; Profiles](02-profiles.md) &middot; [Index](../README.md) &middot; [Why your profile file lost &rarr;](04-why-your-profile-file-lost.md)
# 3. Seeing precedence instead of reasoning about it
Endpoint: [`PrecedenceEndpoint`](../src/main/java/com/ankurm/profiles/web/PrecedenceEndpoint.java).
Transcript: [`01-precedence.txt`](output/01-precedence.txt).
Set `demo.greeting` from five places at once and ask which won:
```
$ DEMO_GREETING=from-environment-variable \
java -Ddemo.greeting=from-system-property \
-jar profiles-and-config-1.0.0.jar --spring.profiles.active=prod \
--demo.greeting=from-command-line-argument
```
```
"effectiveValue": "from-command-line-argument",
"activeProfiles": ["prod", "prod-db", "prod-metrics"],
"holders": [
{ "rank": 1, "value": "from-command-line-argument", "source": "commandLineArgs" },
{ "rank": 2, "value": "from-system-property", "source": "systemProperties" },
{ "rank": 3, "value": "from-environment-variable", "source": "systemEnvironment" },
{ "rank": 4, "value": "from-application-prod-yaml", "origin": "application-prod.yaml - 4:13" },
{ "rank": 5, "value": "from-application-yaml", "origin": "application.yaml - 21:13" }
],
"shadowedCount": 4
```
Five sources hold the property. Four of them lose. Each one that came from a file names its
line.
## The whole implementation
```java
for (ConfigurationPropertySource source : ConfigurationPropertySources.get(environment)) {
ConfigurationProperty property =
source.getConfigurationProperty(ConfigurationPropertyName.of(name));
if (property != null) {
// rank = position, property.getValue(), property.getOrigin()
}
}
```
`ConfigurationPropertySources.get(...)` returns the sources in precedence order. Iterate,
collect every hit, and the first is the winner. That is the entire diagnostic.
## Why this beats reading the list
The documented order is correct but abstract. It does not tell you that a `DEMO_GREETING` left
over from a shell three weeks ago is sitting at rank 3, and that is the actual question.
## Alternatives if you would rather not add an endpoint
- Actuator's `/actuator/env` gives the same information with sanitisation, and
`/actuator/env/{name}` narrows to one property. Prefer it in anything real.
- `logging.level.org.springframework.boot.context.config=TRACE` logs which config data
resources were loaded and in what order.
- `--debug` does *not* show this. It prints the auto-configuration report.

View File

@@ -0,0 +1,73 @@
[&larr; Seeing precedence](03-seeing-precedence.md) &middot; [Index](../README.md) &middot; [Config import &rarr;](05-config-import.md)
# 4. Why your profile-specific file lost
Transcript: [`02-profile-file-loses.txt`](output/02-profile-file-loses.txt).
The bug: you set a value in `application-prod.yaml`, deploy with `prod` active, and the old
value is still in effect.
## Two runs, one difference
```
--- 1. prod profile active, no environment variable ---
effective value : jdbc:postgresql://prod-db:5432/orders
1. jdbc:postgresql://prod-db:5432/orders <- application-prod.yaml
2. jdbc:h2:mem:default <- application.yaml
```
Working as intended. Now with one leftover variable in the environment:
```
--- 2. identical, plus one leftover environment variable ---
effective value : jdbc:postgresql://leftover:5432/orders
1. jdbc:postgresql://leftover:5432/orders <- systemEnvironment
2. jdbc:postgresql://prod-db:5432/orders <- application-prod.yaml
3. jdbc:h2:mem:default <- application.yaml
```
The profile file was still loaded. It still holds the right value. It is at rank 2.
## Why it feels wrong
Profile-specific files *do* override — the mental model is not wrong, it is incomplete. They
override other config data. Config data as a whole sits at item 3 in the precedence list and
environment variables at item 5, so the strongest file loses to the weakest variable.
## Where the leftover variables come from
Every one of these is real:
- A Kubernetes `Deployment` with an `env:` block that predates the ConfigMap and was never
removed. `envFrom` a `ConfigMap` produces environment variables, not config data.
- A `docker-compose.yml` `environment:` entry copied from a colleague.
- Spring Cloud Kubernetes or a service mesh injecting `SPRING_DATASOURCE_URL`.
- A CI runner exporting variables for a different service.
- `SPRING_APPLICATION_JSON`, which is item 10 and beats almost everything.
## Diagnosing it in one step
If a property is not what the file says, look for a variable:
```bash
kubectl exec deploy/my-app -- env | grep -i datasource
```
or ask the running application, which reports every holder including the one you did not know
about.
## Living with it
**Prefer environment variables in containers, files for defaults.** The precedence order is
designed for exactly this: the image carries defaults, the deployment overrides them. Fighting
it means fighting the design.
**Do not set the same key in both places.** If a value is per-environment, keep it out of the
profile files entirely so there is only ever one source.
**Name environment variables specifically.** `DEMO_DATASOURCE_URL` collides with nothing;
`SPRING_DATASOURCE_URL` collides with every Spring application on the host.
**Mount configuration as a config tree instead.** Still config data, still below environment
variables, but at least it is one mechanism rather than two —
[chapter 6](06-config-trees-and-configmaps.md).

View File

@@ -0,0 +1,58 @@
[&larr; Why your profile file lost](04-why-your-profile-file-lost.md) &middot; [Index](../README.md) &middot; [Config trees &rarr;](06-config-trees-and-configmaps.md)
# 5. `spring.config.import`
Transcript: [`04-import-and-multidoc.txt`](output/04-import-and-multidoc.txt).
```yaml
spring:
config:
import: "optional:classpath:/imported.yaml"
```
## The imported file wins
This is the part that catches people, and it catches them in the direction opposite to the one
they brace for:
```
effective value : from-imported-yaml
1. from-imported-yaml <- imported.yaml
2. from-application-import-yaml <- application-import.yaml (declared the import)
3. from-application-yaml <- application.yaml
```
The importing file declared the import and then lost to it. An imported document is processed
*after* the document that declared it, and later documents win.
`#include` semantics would give the opposite. So would treating the import as a set of
defaults, which is what people usually intend when they import a shared baseline. If you import
a company-wide `common.yaml` expecting your own file to override it, every key `common.yaml`
sets will quietly beat yours.
To get defaults-style behaviour, put your overrides somewhere that outranks config data — an
environment variable or a command-line argument — or import from a *later* document in your own
file so the ordering is explicit.
## Prefixes
| Prefix | Meaning |
|---|---|
| `optional:` | do not fail if it is missing |
| `file:` | a filesystem path |
| `classpath:` | a classpath resource |
| `configtree:` | a directory of value-per-file entries |
They compose: `optional:configtree:/etc/config/`.
Without `optional:`, a missing location is `ConfigDataLocationNotFoundException` at startup.
That is usually what you want for a secret mount and never what you want for a developer
machine.
## Where imports are legal
`spring.config.import` is only honoured in config data — `application.yaml` and friends. Setting
it as an environment variable or a command-line argument works too, because those are processed
before config data is loaded. Setting it anywhere else does nothing.
Imports are processed depth-first, and a cycle is detected and reported rather than looping.

View File

@@ -0,0 +1,85 @@
[&larr; Config import](05-config-import.md) &middot; [Index](../README.md)
# 6. Config trees and Kubernetes ConfigMaps
Transcript: [`03-config-tree.txt`](output/03-config-tree.txt).
## What Kubernetes actually mounts
A ConfigMap mounted as a volume is not a properties file. Kubernetes writes **one file per
key**, named after the key, containing only the value with no trailing newline:
```
<configmap-mount>/demo.datasource-url
<configmap-mount>/demo.greeting
<configmap-mount>/demo.pool-size
<configmap-mount>/demo/nested/value
<secret-mount>/demo.api-key
```
```
$ cat <configmap-mount>/demo.greeting
from-configmap-volume
```
There is no syntax to parse. The filename is the key.
## Reading it
```
--spring.config.import=configtree:/etc/config/,configtree:/etc/secrets/
```
A trailing `/` is required — the location is a directory. Values arrive as properties:
```
demo.pool-size = 25
demo.nested.value = from-nested-directory
demo.api-key = sk_live_not_a_real_key
```
A directory under the mount becomes a nested property, so `demo/nested/value` is
`demo.nested.value`. That is how a ConfigMap whose keys contain slashes arrives.
Secrets mount identically. The only difference is file permissions, which is why the same
mechanism reads both and why nothing in your application needs to know which it got.
## Why this beats mounting a properties file
- **Per-key updates.** Changing one key rewrites one file. Kubernetes propagates it to the
volume without a restart, and `spring.config.import` supports `configtree` reloading through
Spring Cloud Kubernetes if you want to act on it.
- **No parse step**, so no chance of one malformed line taking out the whole file.
- **Secrets and config read the same way.**
- **Values can contain anything.** No escaping, no quoting, no YAML surprises — a value of
`yes` stays the string `yes`.
## Wildcards
```
--spring.config.import=optional:configtree:/etc/config/*/
```
Reads every immediate subdirectory, which is the shape you get when several ConfigMaps are
mounted under one parent. Useful for "one ConfigMap per component" layouts.
## It is still config data
An imported config tree outranks `application.yaml` — and still loses to an environment
variable:
```
effective value : from-environment-variable
1. from-environment-variable <- systemEnvironment
2. from-configmap-volume <- ConfigTreePropertySource
3. from-application-yaml <- application.yaml
```
If you mount a ConfigMap *and* set `envFrom` on the same Deployment — which is a common way to
migrate from one to the other — the environment variables win and the ConfigMap looks broken.
## There is no profile-specific config tree
No `<mount>-prod` convention exists. Per-environment configuration is a different ConfigMap
chosen by the Deployment, not by `spring.profiles.active`. This is a feature: the environment
is decided by what you deploy, not by a string inside the image.

View File

@@ -0,0 +1,83 @@
== every source sets demo.greeting at once ==
$ DEMO_GREETING=from-environment-variable \
java -Ddemo.greeting=from-system-property \
-jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod \
--demo.greeting=from-command-line-argument
{
"property": "demo.greeting",
"effectiveValue": "from-command-line-argument",
"activeProfiles": [
"prod",
"prod-db",
"prod-metrics"
],
"holders": [
{
"rank": 1,
"source": "SimpleCommandLinePropertySource {name='commandLineArgs'}",
"value": "from-command-line-argument",
"origin": "\"demo.greeting\" from property source \"commandLineArgs\""
},
{
"rank": 2,
"source": "PropertiesPropertySource {name='systemProperties'}",
"value": "from-system-property",
"origin": "\"demo.greeting\" from property source \"systemProperties\""
},
{
"rank": 3,
"source": "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}",
"value": "from-environment-variable",
"origin": "System Environment Property \"DEMO_GREETING\""
},
{
"rank": 4,
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/''}",
"value": "from-application-prod-yaml",
"origin": "class path resource [application-prod.yaml] from profiles-and-config-1.0.0.jar - 4:13"
},
{
"rank": 5,
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/''}",
"value": "from-application-yaml",
"origin": "class path resource [application.yaml] from profiles-and-config-1.0.0.jar - 21:13"
}
],
"shadowedCount": 4
}
== and with the environment variable removed, nothing else changed ==
./scripts/demo-precedence.sh: line 41: 5735 Killed DEMO_GREETING=from-environment-variable setsid nohup java -Ddemo.greeting=from-system-property -jar "$JAR" --spring.profiles.active=prod --demo.greeting=from-command-line-argument > /tmp/profiles-precedence.log 2>&1 < /dev/null
{
"property": "demo.greeting",
"effectiveValue": "from-system-property",
"activeProfiles": [
"prod",
"prod-db",
"prod-metrics"
],
"holders": [
{
"rank": 1,
"source": "PropertiesPropertySource {name='systemProperties'}",
"value": "from-system-property",
"origin": "\"demo.greeting\" from property source \"systemProperties\""
},
{
"rank": 2,
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/''}",
"value": "from-application-prod-yaml",
"origin": "class path resource [application-prod.yaml] from profiles-and-config-1.0.0.jar - 4:13"
},
{
"rank": 3,
"source": "OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/''}",
"value": "from-application-yaml",
"origin": "class path resource [application.yaml] from profiles-and-config-1.0.0.jar - 21:13"
}
],
"shadowedCount": 2
}
./scripts/demo-precedence.sh: line 41: 5796 Killed setsid nohup java -Ddemo.greeting=from-system-property -jar "$JAR" --spring.profiles.active=prod > /tmp/profiles-precedence2.log 2>&1 < /dev/null

View File

@@ -0,0 +1,41 @@
== does application-prod.yaml win? ==
demo.datasource-url is set in application.yaml and again in application-prod.yaml.
--- 1. prod profile active, no environment variable ---
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod
active profiles : prod, prod-db, prod-metrics
effective value : jdbc:postgresql://prod-db:5432/orders
1. jdbc:postgresql://prod-db:5432/orders <- 'file application-prod.yaml' via location 'optional:classpath:/''}
2. jdbc:h2:mem:default <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 1
--- 2. identical, plus one leftover environment variable ---
$ DEMO_DATASOURCE_URL=jdbc:postgresql://leftover:5432/orders \
java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=prod
active profiles : prod, prod-db, prod-metrics
effective value : jdbc:postgresql://leftover:5432/orders
1. jdbc:postgresql://leftover:5432/orders <- OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
2. jdbc:postgresql://prod-db:5432/orders <- 'file application-prod.yaml' via location 'optional:classpath:/''}
3. jdbc:h2:mem:default <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 2
The profile-specific file is still loaded and still holds its value -- it is listed,
and it lost. Config data is item 3 in the documented precedence list; OS environment
variables are item 5, and later items win.
== the full property-source stack, in order ==
$ curl -s localhost:8080/sources
1. MapPropertySource server.ports
2. ConfigurationPropertySourcesPropertySource configurationProperties
3. SimpleCommandLinePropertySource commandLineArgs
4. StubPropertySource servletConfigInitParams
5. ServletContextPropertySource servletContextInitParams
6. PropertiesPropertySource systemProperties
7. OriginAwareSystemEnvironmentPropertySource systemEnvironment
8. RandomValuePropertySource random
9. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod-metrics.yaml]' via location 'optional:classpath:/'
10. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod-db.yaml]' via location 'optional:classpath:/'
11. OriginTrackedMapPropertySource Config resource 'class path resource [application-prod.yaml]' via location 'optional:classpath:/'
12. OriginTrackedMapPropertySource Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/'
13. ApplicationInfoPropertySource applicationInfo

View File

@@ -0,0 +1,45 @@
== what Kubernetes actually mounts ==
$ find /tmp/demo-configmap /tmp/demo-secret -type f | sort
<configmap-mount>/demo.datasource-url
<configmap-mount>/demo.greeting
<configmap-mount>/demo.pool-size
<configmap-mount>/demo/nested/value
<secret-mount>/demo.api-key
$ cat <configmap-mount>/demo.greeting; echo
from-configmap-volume
Each file holds a bare value with no trailing newline and no key. There is no
properties syntax to parse -- the filename is the key.
== importing it ==
$ java -jar target/profiles-and-config-1.0.0.jar \
--spring.config.import=configtree:/tmp/demo-configmap/,configtree:/tmp/demo-secret/
active profiles : (none)
effective value : from-configmap-volume
1. from-configmap-volume <- ConfigTreePropertySource {name='Config tree '/tmp/demo-configmap''}
2. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 1
demo.pool-size = 25
demo.nested.value = from-nested-directory
demo.api-key = sk_live_not_a_real_key
A directory under the mount becomes a nested property: demo/nested/value is
demo.nested.value. That is how a ConfigMap with slashes in its keys arrives.
== the part that surprises people ==
An imported config tree outranks application.yaml, but it is still config data,
so it still loses to an environment variable:
active profiles : (none)
effective value : from-environment-variable
1. from-environment-variable <- OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
2. from-configmap-volume <- ConfigTreePropertySource {name='Config tree '/tmp/demo-configmap''}
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 2
There is also no such thing as a profile-specific config tree. There is no
<mount>-prod directory convention; a per-environment ConfigMap is a different mount
chosen by the deployment, not by spring.profiles.active.

View File

@@ -0,0 +1,60 @@
== spring.config.import: which document wins? ==
application-import.yaml imports imported.yaml. Both set demo.greeting.
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=import
active profiles : import
effective value : from-imported-yaml
1. from-imported-yaml <- 'file imported.yaml' via location 'optional:classpath:/imported.yaml''}
2. from-application-import-yaml <- 'file application-import.yaml' via location 'optional:classpath:/''}
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 2
demo.imported-only = yes-this-file-was-read
The imported file WON. spring.config.import does not behave like #include, and it
does not behave like a default either: the imported document is processed AFTER the
document that declared the import, so it outranks the file that pulled it in.
If you import a shared baseline expecting your own file to override it, every key
the baseline sets will quietly beat yours.
== one file, several documents, activated by condition ==
--- spring.profiles.active=<none> (with the multidoc profile) ---
active profiles : multidoc
effective value : from-multidoc-default-document
1. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
2. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 1
--- spring.profiles.active=staging (with the multidoc profile) ---
active profiles : multidoc, staging
effective value : from-multidoc-staging-document
1. from-multidoc-staging-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
2. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
3. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 2
--- spring.profiles.active=prod (with the multidoc profile) ---
active profiles : multidoc, prod, prod-db, prod-metrics
effective value : from-application-prod-yaml
1. from-application-prod-yaml <- 'file application-prod.yaml' via location 'optional:classpath:/''}
2. from-multidoc-prod-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
3. from-multidoc-default-document <- 'file application-multidoc.yaml' via location 'optional:classpath:/' (document ...
4. from-application-yaml <- 'file application.yaml' via location 'optional:classpath:/''}
holders that lost: 3
Later documents in the same file win over earlier ones, so the unconditional first
document acts as the default and each conditional document overrides it.
== the activation Spring Boot refuses ==
application-badactivation.yaml tries to set spring.profiles.active from a document
that is itself conditional on a profile.
$ java -jar target/profiles-and-config-1.0.0.jar --spring.profiles.active=badactivation,staging
org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property
'spring.profiles.active' imported from location 'class path resource
[application-badactivation.yaml]' is invalid in a profile specific resource [origin: class path
resource [application-badactivation.yaml] from profiles-and-config-1.0.0.jar - 12:13]
at
org.springframework.boot.context.config.InvalidConfigDataPropertyException.lambda$throwIfPropert
yFound$1(InvalidConfigDataPropertyException.java:123)