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?