Describe the bug
Spring Boot 3 Native - "Could not bind properties" error when properties class located in another module uses @ConstructorBinding in multi-module gradle project
To Reproduce Environment - Spring Boot: 3.0.0-RC2 - Native Buildtools: 0.9.17 - GraalVM version : graalvm-ce-java17-22.3.0 - JDK version: openjdk 17.0.5 - Architecture: AMD64
Sample project https://github.com/michalkrajcovic/spring-native-multi-modules-bind-properties
Compile
./gradlew nativeCompile
Run
./greetings-app/build/native/nativeCompile/greetings-app
Fails with
o.s.c.support.GenericApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'greetings-com.example.greetings.configuration.GreetingsProperties': Could not bind properties to 'GreetingsProperties' : prefix=greetings, ignoreInvalidFields=false, ignoreUnknownFields=true
Description:
Failed to bind properties under 'greetings' to com.example.greetings.configuration.GreetingsProperties:
Reason: java.lang.IllegalStateException: Failed to extract parameter names for public com.example.greetings.configuration.GreetingsProperties(java.lang.String)
Runs without issues on JVM
./gradlew bootRun
Expected behavior Application starts without issues using JVM and native image.
Observation Does not fail when properties class uses setters see brach setter. Also ConstructorBinding doesn't fail in single module project see branch single-module
Originally reported for Spring Boot 2.7.5 in spring-native
Comment From: snicoll
@michalkrajcovic thanks for the report. GreetingsProperties must be annotated with @NestedConfigurationProperties to indicate to the inference algorithm that it isn't a scalar value but a "sub-namespace".
See #31708.
Comment From: michalkrajcovic
@snicoll actually, this is not nested property class, as described in the docs here. Please see the properties class here it has only one field, type java.lang.String.
Comment From: snicoll
Apologizes for this, I must have misread something but I can't tell what now.
Comment From: snicoll
The error is
Caused by: java.lang.IllegalStateException: Failed to extract parameter names for public com.example.greetings.configuration.GreetingsProperties(java.lang.String)
at org.springframework.util.Assert.state(Assert.java:97) ~[na:na]
at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.parseConstructorParameters(ValueObjectBinder.java:270) ~[na:na]
at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.<init>(ValueObjectBinder.java:264) ~[na:na]
at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.get(ValueObjectBinder.java:291) ~[na:na]
at org.springframework.boot.context.properties.bind.ValueObjectBinder$ValueObject.get(ValueObjectBinder.java:198) ~[greetings-app:3.0.0-RC2]
at org.springframework.boot.context.properties.bind.ValueObjectBinder.bind(ValueObjectBinder.java:67) ~[greetings-app:3.0.0-RC2]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:476) ~[greetings-app:3.0.0-RC2]
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:590) ~[na:na]
at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:576) ~[na:na]
at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:474) ~[greetings-app:3.0.0-RC2]
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:414) ~[greetings-app:3.0.0-RC2]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:343) ~[greetings-app:3.0.0-RC2]
... 39 common frames omitted
There is a reflection hint for GreetingProperties:
{
"name": "com.example.greetings.configuration.GreetingsProperties",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
}
]
}
It feels like --parameter wasn't set or something.
Comment From: wilkinsona
It feels like --parameter wasn't set or something.
I think that's exactly it. Boot's Gradle plugin sets -parameters but it hasn't been applied to the greetings-configuration project. I suspect that it works on the JVM as we're able to fall back to the LocalVariableTableParameterNameDiscoverer. That fallback would only work in a native image if the class files were made available as resources.
Comment From: wilkinsona
The sample works with this addition to the greetings-configuration project:
tasks.named('compileJava').configure {
options.compilerArgs = ['-parameters']
}
There's no way for us to do this automatically as Boot's plugin hasn't been applied (and nor should it have been in this case). I wonder if there's something else that we could do to make this work automatically. If including the class files as resources works, perhaps we could generate hints that do that if we detect that the standard reflection parameter information is missing?
Comment From: wilkinsona
If including the class files as resources works, perhaps we could generate hints that do that if we detect that the standard reflection parameter information is missing?
I've prototyped this and it works. I'd like to discuss this with team to see if it's something that we want to do or if we'd prefer to document the need to compile with -parameters when targeting a native image.
Comment From: wilkinsona
We discussed this today and concluded that relying on .class resources and ASM-based processing isn't the right choice for a native image. We're going to document the need for -parameters and also fail fast at build time when we detect that it hasn't been used.