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
3.2 KiB
← When @Value wins · Index · Diagnosing a value →
7. IDE metadata, and the JDK 23 change that silently breaks it
Transcript: 05-metadata-generation.txt.
What the metadata is
spring-boot-configuration-processor is an annotation processor. At compile time it reads your
@ConfigurationProperties types and writes META-INF/spring-configuration-metadata.json:
{
"groups": [
{ "name": "demo.mail", "type": "com.ankurm.configprops.props.MailProperties" }
],
"properties": [
{ "name": "demo.mail.port", "type": "java.lang.Integer", "defaultValue": 587 }
]
}
That file is what makes property names auto-complete in an IDE, and what shows the Javadoc on a record component as hover documentation. Nothing at runtime reads it.
The failure
Declaring the processor as a dependency — the way every tutorial written before 2024 shows — stops working on JDK 23 and later:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Absent any processor-related command-line option, -proc:none is now javac's default. JDK 21
began printing an informative message when implicit annotation processing was detected, and
JDK 23 turned the policy off, with the stated goal of making builds robust against processors
landing on the classpath unintentionally. A processor that is only on the classpath is now
simply not run.
The build still succeeds. The jar is still valid. The application behaves identically. The only symptom is that property auto-completion quietly stops working, which is the kind of thing people blame on the IDE.
Two compilations of identical sources with the identical processor jar:
A) javac -cp <deps>:spring-boot-configuration-processor.jar -d a $SOURCES
spring-configuration-metadata.json files produced: 0
B) javac -proc:full -cp <deps>:spring-boot-configuration-processor.jar -d b $SOURCES
spring-configuration-metadata.json files produced: 1
The fix
Declare it as an annotation processor path. That makes Maven pass --processor-path, and an
explicit processor option re-enables processing -- the new default only applies when javac is
given nothing to go on:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${project.parent.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
-proc:full also works and is a smaller change, but it restores the old policy for every
processor on the classpath, which is the behaviour that was turned off for a reason.
Checking
ls target/classes/META-INF/spring-configuration-metadata.json. If it is not there, the
processor did not run. Add @ConfigurationProperties metadata to your build's definition of
done, because nothing else will tell you.