My spring boot 3.2.4 project doesn't start up when built native if my technical library contains an EnvironmentPostProcessor linked in spring.factories resource. If I get rid of both from the library and just copy paste the same processor in the main project repo and build it again, it works fine.
The application doesn't start either locally nor from a kubernetes POD. It just exits at startup, no logs at all are printed out:
docker --debug run --rm -p 8080 my-docker-repo:30501/repository/docker/my-service:latest
DEBU[0000] [hijack] End of stdout
My POM looks like this:
<parent>
<groupId>com.xxx</groupId>
<artifactId>my-parent</artifactId>
<version>0.0.33</version>
</parent>
<artifactId>my-service<artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
...
</properties>
<dependencies>
<dependency>
<groupId>com.xxx</groupId>
<artifactId>my-tech-starter</artifactId>
<version>0.0.42-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-random-core</artifactId>
<version>${easy-random.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
While my-tech-starter has a bunch of spring boot / cloud dependencies but what really makes the diff looks to be this EnvironmentPostProcessor linked in the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.xxx.techstarter.processor.SpringConfigImportEnvironmentPostProcessor
If I removed those 2 files, it works. Even if I copy the SpringConfigImportEnvironmentPostProcessor in the main project. Note that the library is build with -Pnative flag and I can see the /META-INF/native-image/ inside the JAR. If i do a standard, non native build, it works fine in any case.
Any idea? Thanks a lot
Comment From: scottfrederick
Thanks for getting in touch. Unfortunately there's not enough information here for us to understand exactly what you are doing. If you would like us to spend some time investigating, please provide a complete minimal sample that reproduces the problem. The reproducer should have the fewest dependencies possible, ideally without any Spring Cloud dependencies. You can share it with us by pushing it to a separate repository on GitHub or by zipping it and attaching it to this issue.
Comment From: danparisi
Dear @scottfrederick, Thanks for your quick reply.
I managed to create a very small example reproducing the issue: https://github.com/danparisi/spring-boot-native-environment-post-processor-issue
Instructions about how to run it are in the README, please let me know if anything is missing.
Thanks for the support
Comment From: wilkinsona
Your environment post-processed relies on reflection through its use of SnakeYAML and you haven't provided any reachability metadata to convey that information to GraalVM. You can see the failure by wrapping the body of your postProcessEnvironment in a try-catch block:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
try {
…
}
catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
Running the native image will then show the failure:
Can't construct a java object for tag:yaml.org,2002:com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor$SpringProperties; exception=java.lang.NoSuchMethodException: com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor$SpringProperties.<init>()
in 'reader', line 1, column 1:
spring:
^
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:326)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObjectNoCheck(BaseConstructor.java:264)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:247)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:201)
at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:185)
at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:493)
at org.yaml.snakeyaml.Yaml.load(Yaml.java:434)
at com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor.getSpringPropertiesValue(SpringConfigImportEnvironmentPostProcessor.java:75)
at com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor.postProcessEnvironment(SpringConfigImportEnvironmentPostProcessor.java:39)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:109)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:94)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
at java.base@21/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:369)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:329)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
at com.myservice.myservice.Application.main(Application.java:11)
at java.base@21/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.yaml.snakeyaml.error.YAMLException: java.lang.NoSuchMethodException: com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor$SpringProperties.<init>()
at org.yaml.snakeyaml.constructor.BaseConstructor.newInstance(BaseConstructor.java:382)
at org.yaml.snakeyaml.constructor.BaseConstructor.newInstance(BaseConstructor.java:344)
at org.yaml.snakeyaml.constructor.BaseConstructor.newInstance(BaseConstructor.java:340)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.construct(Constructor.java:164)
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:320)
... 27 more
Caused by: java.lang.NoSuchMethodException: com.myservice.mytechlibrary.processor.SpringConfigImportEnvironmentPostProcessor$SpringProperties.<init>()
at java.base@21/java.lang.Class.checkMethod(DynamicHub.java:1065)
at java.base@21/java.lang.Class.getConstructor0(DynamicHub.java:1228)
at java.base@21/java.lang.Class.getDeclaredConstructor(DynamicHub.java:2930)
at org.yaml.snakeyaml.constructor.BaseConstructor.newInstance(BaseConstructor.java:376)
... 31 more
You need to allow instances of SpringProperties to be created using reflection.
Unfortunately, errors like this that occur before logging has been set up need to be handled carefully, particularly in a native image. We may be able to improve the diagnostics here, but I'm not certain that we can.