Bug description
Native compilation AOT step fails for native compilation on @ConfigurationProperty of record type enabled by @EnableConfigurationProperty
I observed that the definition of second constructor for property class(record) causing compilation error.
Exception in thread "main" java.lang.IllegalStateException: No constructor or factory method candidate found for Root bean: class [com.example.graalvmdemo.config.RecordProperties]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null and argument types []
Bug reproduce
-
Use following dummy app: https://github.com/adamkubon/graalvm-demo
-
command used to compile the project
# mvn clean install -DskipTests; mvn -Pnative native:compile -DskipTests
Expected behaviour
No error during compilation
System info
- Ubuntu 22.04.3 LTS (hosted on vmware)
- GraalVM Version 17.0.8-graalce (installed by sdkman)
- Java Version 17
Comment From: wilkinsona
Thanks for the sample, I've reproduced the problem. The stack trace of the failure is the following:
Exception in thread "main" java.lang.IllegalStateException: No constructor or factory method candidate found for Root bean: class [com.example.graalvmdemo.config.RecordProperties]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null and argument types []
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorOrFactoryMethod(ConstructorResolver.java:947)
at org.springframework.beans.factory.support.RegisteredBean.resolveConstructorOrFactoryMethod(RegisteredBean.java:212)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.<init>(BeanDefinitionMethodGenerator.java:86)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:100)
at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:115)
at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:49)
at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:37)
at org.springframework.context.aot.BeanFactoryInitializationAotContributions.getContributions(BeanFactoryInitializationAotContributions.java:67)
at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:49)
at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:44)
at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:58)
at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:106)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82)
at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
The failure is occurring because BeanDefinitionMethodGenerator is trying to resolve the constructor or factory method that will create the RecordProperties instance and it cannot do so. It cannot do so because the constructor that will be used is determined by configuration property binding which BeanDefinitionMethodGenerator knows nothing about. Calling this constructor is handled (indirectly) by the code generated by ConfigurationPropertiesBeanRegistrationCodeFragments. This generated code (largely) removes the need for BeanDefinitionMethodGenerator to know the constructor or factory method so, ideally, it wouldn't even try to resolve it. We'll transfer this issue to the Framework team so that they can investigate this possibility.
I think it's worth noting that the problem will only occur when the record has multiple constructors and that removing the constructor that has a single parameter works around the problem. With only a single constructor, RecordProperties(boolean, boolean) is resolved and then never used by Framework code to create the RecordProperties instance. Instead that creation is performed by the configuration property binder.
Comment From: snicoll
See also https://github.com/spring-projects/spring-framework/issues/31117
Comment From: adamkubon
So I've reproduced the problem in a dummy app shape, because I've try to use the spring-cloud-kubernetes-discovery library where KubernetesDiscoveryProperties class is defined simillary (as a record) with the multiple constructors and compilation failing in the same way.
see following link to library class: https://github.com/vaquarkhan/spring-cloud-kubernetes-1/blob/master/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryProperties.java
I agree it's easy to work-around if you own the code but it can be much more painfull if we try to use third-party library.
It's difficult to say they make a mistake using record type in the library development. In a formal point of view the mentioned library has no issues, unless you try to compile the native app.
Comment From: bclozel
Closing as a duplicate of spring-projects/spring-framework#31117
Comment From: snicoll
While this is indeed a duplicate of https://github.com/spring-projects/spring-framework/issues/31117, I am not sure we will be able to fix the faulty behavior in a framework maintenance release. However, we could workaround the issue on our side by hinting the framework at which constructor to use, rather than letting it trying to resolve the "autowired" constructor.
I've prototyped that in https://github.com/snicoll/spring-boot/commit/04975f61e597b344529236b9491207510c984b09