1
0
Files
spring-boot-demo/configuration-properties/docs/05-validation.md
Ankur Mhatre 86246dc860 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
2026-09-08 16:47:48 +00:00

56 lines
2.3 KiB
Markdown

[&larr; Records and defaults](04-records-and-defaults.md) &middot; [Index](../README.md) &middot; [When @Value wins &rarr;](06-when-value-still-wins.md)
# 5. Validation
Transcript: [`04-validation-failure.txt`](output/04-validation-failure.txt).
Type: [`ValidatedProperties`](../src/main/java/com/ankurm/configprops/props/ValidatedProperties.java).
This is the capability `@Value` does not have at all, and the reason to prefer binding for
anything that can be misconfigured.
## What it takes
1. `spring-boot-starter-validation` on the classpath. Without a validator implementation,
`@Validated` is a no-op — nothing is checked and nothing says so.
2. `@Validated` on the properties type.
3. JSR-380 constraints on the components.
## What you get
A bad value becomes a startup failure that names the property, the offending value, the file
and line it came from, and the constraint it broke — all of them at once, not one per restart:
```
Property: demo.validated.port
Value: "99999"
Origin: class path resource [application-badvalidation.yaml] - 6:11
Reason: must be less than or equal to 65535
```
The alternative is a `NumberFormatException` in a request handler at 3am.
## `@Valid` on nested types is not what makes them validate
The rule "annotate nested properties with `@Valid` or their constraints are ignored" is widely
repeated and does not apply here. It is true of ordinary bean validation, where cascading is
opt-in. Spring Boot's `ValidationBindHandler` validates *every object the binder finishes
constructing*, nested ones included.
`BindingContractTests.nestedConstraintsFireWithoutValid` pins this: a nested record carrying
`@Max(100)` and no `@Valid` anywhere in the type still fails the bind at 4000. That test was
originally written to assert the opposite and failed, which is how it ended up documented here.
Keep writing `@Valid` if you like — it is harmless and it is what a reader expects. Just do not
believe it is load-bearing.
## Ordering
The violation order in the failure report is not stable between runs. Do not write a test that
asserts on it.
## Custom validation
For rules a constraint annotation cannot express — "if `mode` is `remote` then `endpoint` is
required" — implement `Validator` and expose it as a bean named `configurationPropertiesValidator`.
It runs at bind time with the same reporting.