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
2.9 KiB
← Seeing precedence · Index · Config import →
4. Why your profile-specific file lost
Transcript: 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
Deploymentwith anenv:block that predates the ConfigMap and was never removed.envFromaConfigMapproduces environment variables, not config data. - A
docker-compose.ymlenvironment: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:
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.