Describe the issue
I have a Spring Boot 3 (version 3.2.1) app with nothing on the classpath except spring-boot-starter
.
if I use following code:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
ExecutorService executorService() {
return Executors.newFixedThreadPool(10);
}
}
...and then compile as Native image:
mvn clean native:compile -Pnative
...and run:
./target/demo
...then I get the following error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'executorService': Invalid destruction signature
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:643) ~[demo:6.1.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[demo:6.1.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[demo:6.1.2]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[demo:6.1.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[demo:6.1.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[demo:6.1.2]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[demo:6.1.2]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:960) ~[demo:6.1.2]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[demo:6.1.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) ~[demo:3.2.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:464) ~[demo:3.2.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[demo:3.2.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1358) ~[demo:3.2.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1347) ~[demo:3.2.1]
at com.example.demo.DemoApplication.main(DemoApplication.java:15) ~[demo:na]
Caused by: org.springframework.beans.factory.support.BeanDefinitionValidationException: Could not find a destroy method named 'shutdown' on bean with name 'executorService'
at org.springframework.beans.factory.support.DisposableBeanAdapter.<init>(DisposableBeanAdapter.java:134) ~[na:na]
at org.springframework.beans.factory.support.AbstractBeanFactory.registerDisposableBeanIfNecessary(AbstractBeanFactory.java:1868) ~[demo:6.1.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:639) ~[demo:6.1.2]
And if I'll switch bean type to a precise one: (ThreadPoolExecutor
) instead of (ExecutorService
), then it works:
// This works
@Bean
ThreadPoolExecutor executorService() {
return (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
}
// This doesn't work
@Bean
ExecutorService executorService() {
return Executors.newFixedThreadPool(10);
}
This issue is almost exactly the same like this one: #29545 except that now it only reproducing for ExecutorService
.
Steps to reproduce the issue Please see reproducible Demo app: spring-native-executor-demo.zip
Describe GraalVM and your environment: - GraalVM version: 17.0.9-graal - JDK major version: 17 - OS: macOS Sonoma 14.1.1 (23B81) - Architecture: ARM64
Comment From: sdeleuze
Looks indeed very similar to #29545, especially given the fact that ExecutorService
has the void shutdown()
method defined at interface level.
Surprisingly, I can't reproduce with using SDKman with sdk use java 23.1.1.r21-nik
or sdk use java 21.0.1-graalce
, but I can reproduce using sdk use java 17.0.9-graalce
. On Spring AOT side, the metadata for ExecutorService#shutdown
are generated as expected.
That likely indicates that this is a GraalVM limitation or bug only fixed in recent versions.
As consequence, and since we don't have any actionable item on Spring side, I close this ticket.
I advise you to use a more recent GraalVM distribution or add invoke reflection hints on ThreadPoolExecutor#shutdown
.
Comment From: RomanCht
@sdeleuze thank you for quick investigation!
I see that for different java versions the metadata for ExecutorService
's Destroy method generated differently.
For the Java 17 (sdk use java 17.0.9-graalce
) metadata looks like this:
{
"name": "java.util.concurrent.ExecutorService",
"queryAllPublicMethods": true,
"queryAllDeclaredMethods": true,
"methods": [
{
"name": "shutdown", <<== one method, which is not default in ExecutorService interface
"parameterTypes": [ ]
}
]
},
...but for the Java 21 (sdk use java 21.0.1-graalce
) metadata looks like this:
{
"name": "java.util.concurrent.ExecutorService",
"queryAllPublicMethods": true,
"queryAllDeclaredMethods": true,
"methods": [
{
"name": "close", <<== new default method
"parameterTypes": [ ]
}
]
},
...because, since the Java 19 ExecutorService
interface extends AutoClosable
, and has default void close()
method.
So, probably this is the issue?
Comment From: sdeleuze
Good catch, this is probably the reason for such difference of behavior. But that's does not explain why the repro for #29545 works and this one on Java 17 doesn't.
After a deeper look, I think I found the cause: #29545 does not attempt to find methods in superclass interfaces. As a consequence, I reopen this issue.
Comment From: jhoeller
@sdeleuze I suppose this should be backported to 6.0.x as well?