Affects: \<6.1.2> Reproducer: https://github.com/juliuskrah/graphql-demo/tree/spf-32472

Missing class in AOT processed Bean

Given the following bean definition:

class MongockBeanDefinitionRegistrar(
    private val environment: Environment
): ImportBeanDefinitionRegistrar {
// ...

    override fun registerBeanDefinitions(
        metadata: AnnotationMetadata,
        registry: BeanDefinitionRegistry,
        importBeanNameGenerator: BeanNameGenerator,
    ) {
      // ...
       val mongockSupportBeanDefinitionBuilder = BeanDefinitionBuilder
            .rootBeanDefinition(MongockRunnerSupport::class.java)
            .addPropertyValue("migrationClasses", changeUnitSets)
            .addPropertyReference("driver", "connectionDriver")
            .addPropertyReference("config", "mongock-io.mongock.runner.springboot.base.config.MongockSpringConfiguration")

        val mongockRunnerBeanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongockRunner::class.java)
            .setFactoryMethodOnBean("create", "mongockRunnerSupport")
            .setInitMethodName("execute")

        registry.registerBeanDefinition(getName(MongockRunner::class.java), mongockRunnerBeanDefinitionBuilder.beanDefinition)
        registry.registerBeanDefinition("mongockRunnerSupport", mongockSupportBeanDefinitionBuilder.beanDefinition)
  }

  class MongockRunnerSupport: ApplicationContextAware, ApplicationEventPublisherAware {
        var driver: ConnectionDriver? = null
        var config: MongockConfiguration? = null
        var migrationClasses: List<Class<*>>? = emptyList()
        private lateinit var applicationContext: ApplicationContext
        private lateinit var eventPublisher: ApplicationEventPublisher

        fun create(): MongockRunner {
            val builder: RunnerSpringbootBuilder = MongockSpringboot.builder()
            if (this.driver != null) builder.setDriver(driver)
            if (this.config != null) builder.setConfig(config)
            builder.setSpringContext(applicationContext)
            builder.setEventPublisher(eventPublisher)
            migrationClasses?.forEach(builder::addMigrationClass)
            return builder.buildRunner()
        }

        override fun setApplicationContext(applicationContext: ApplicationContext) {
            this.applicationContext = applicationContext
        }

        override fun setApplicationEventPublisher(applicationEventPublisher: ApplicationEventPublisher) {
            this.eventPublisher = applicationEventPublisher
        }
    }
}

The aot process generates the following class:

@Generated
public class MongockBeanDefinitionRegistrar__BeanDefinitions {

  @Generated
  public static class MongockRunnerSupport {

    private static BeanInstanceSupplier<MongockRunner> getMongockRunnerInstanceSupplier() {
      return BeanInstanceSupplier.<MongockRunner>forFactoryMethod(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class, "create")
              .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class).create());
    }


    public static BeanDefinition getMongockRunnerBeanDefinition() {
      RootBeanDefinition beanDefinition = new RootBeanDefinition(MongockRunner.class);
      beanDefinition.setTargetType(MongockRunner.class);
      beanDefinition.setInitMethodNames("execute");
      beanDefinition.setInstanceSupplier(getMongockRunnerInstanceSupplier());
      return beanDefinition;
    }


    public static BeanDefinition getMongockRunnerSupportBeanDefinition() {
      RootBeanDefinition beanDefinition = new RootBeanDefinition(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class);
      beanDefinition.getPropertyValues().addPropertyValue("migrationClasses", List.of(CreateProductCollectionChangeUnit202401151530.class));
      beanDefinition.getPropertyValues().addPropertyValue("driver", new RuntimeBeanReference("connectionDriver"));
      beanDefinition.getPropertyValues().addPropertyValue("config", new RuntimeBeanReference("mongock-io.mongock.runner.springboot.base.config.MongockSpringConfiguration"));
      beanDefinition.setInstanceSupplier(MongockBeanDefinitionRegistrar.MongockRunnerSupport::new);
      return beanDefinition;
    }
  }
}

I encounter the following exception when running the native executable:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongockRunnerSupport': Unresolved class: class com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport (kind = CLASS)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1323) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1284) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:486) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334) ~[graphql-demo:6.1.2]
    at com.example.graph.spring.MongockBeanDefinitionRegistrar__BeanDefinitions$MongockRunnerSupport.lambda$getRunnerSpringbootBuilderInstanceSupplier$0(MongockBeanDefinitionRegistrar__BeanDefinitions.java:28) ~[na:na]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[graphql-demo:6.1.2]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:171) ~[na:na]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[graphql-demo:6.1.2]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206) ~[na:na]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[graphql-demo:6.1.2]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:218) ~[na:na]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206) ~[na:na]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1216) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[graphql-demo:6.1.2]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:960) ~[graphql-demo:6.1.2]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[graphql-demo:6.1.2]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[graphql-demo:3.2.1]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) ~[graphql-demo:3.2.1]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:464) ~[graphql-demo:3.2.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[graphql-demo:3.2.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1358) ~[graphql-demo:3.2.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1347) ~[graphql-demo:3.2.1]
    at com.example.graph.ApplicationKt.main(Application.kt:16) ~[graphql-demo:na]
    at java.base@21/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) ~[na:na]
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Unresolved class: class com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport (kind = CLASS)
    at kotlin.reflect.jvm.internal.KClassImpl.createSyntheticClassOrFail(KClassImpl.kt:340) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl.access$createSyntheticClassOrFail(KClassImpl.kt:49) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:67) ~[na:na]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:53) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(KClassImpl.kt:53) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:193) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl.getMemberScope$kotlin_reflection(KClassImpl.kt:202) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:173) ~[na:na]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:173) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(KClassImpl.kt:173) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:182) ~[na:na]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:182) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllNonStaticMembers(KClassImpl.kt:182) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$allMembers$2.invoke(KClassImpl.kt:188) ~[na:na]
    at kotlin.reflect.jvm.internal.KClassImpl$Data$allMembers$2.invoke(KClassImpl.kt:188) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
    at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllMembers(KClassImpl.kt:188) ~[graphql-demo:1.9.21-release-633]
    at kotlin.reflect.jvm.internal.KClassImpl.getMembers(KClassImpl.kt:206) ~[graphql-demo:1.9.21-release-633]
    at org.springframework.data.util.KotlinBeanInfoFactory.getBeanInfo(KotlinBeanInfoFactory.java:64) ~[graphql-demo:3.2.1]
    at org.springframework.beans.CachedIntrospectionResults.getBeanInfo(CachedIntrospectionResults.java:222) ~[na:na]
    at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:248) ~[na:na]
    at org.springframework.beans.CachedIntrospectionResults.forClass(CachedIntrospectionResults.java:157) ~[na:na]
    at org.springframework.beans.BeanWrapperImpl.getCachedIntrospectionResults(BeanWrapperImpl.java:162) ~[graphql-demo:6.1.2]
    at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:193) ~[graphql-demo:6.1.2]
    at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:58) ~[graphql-demo:6.1.2]
    at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyHandler(AbstractNestablePropertyAccessor.java:739) ~[graphql-demo:6.1.2]
    at org.springframework.beans.AbstractNestablePropertyAccessor.isWritableProperty(AbstractNestablePropertyAccessor.java:565) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1686) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1433) ~[graphql-demo:6.1.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[graphql-demo:6.1.2]
    ... 41 common frames omitted

Comment From: sdeleuze

When running docker compose -f src/main/resources/compose.yml -f src/main/resources/compose.dev.yml up, I get a permissions on /docker-entrypoint-initdb.d/keyfile are too open error with Docker 24.0.5, could you please fix it to allow me to run the sample properly?

Comment From: juliuskrah

When running docker compose -f src/main/resources/compose.yml -f src/main/resources/compose.dev.yml up, I get a permissions on /docker-entrypoint-initdb.d/keyfile are too open error with Docker 24.0.5, could you please fix it to allow me to run the sample properly?

Set the permissions on the file

chmod 400 src/main/resources/docker/keyfile

Comment From: sdeleuze

I am able to reproduce, that said, I am not sure something has to be fixed in Spring Framework since org.springframework.data.util.KotlinBeanInfoFactory is triggering this error. But we can try to qualify more precisely this issue.

I am not sure yet why we see this error because com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport seems to have various metadata provided, and even if I add allDeclaredMethods: true and "allPublicMethods": true I still see the same error (see related kotlin-reflect source code).

Comment From: sdeleuze

After more tests, I found that the applications works as expected if you add a reflection hint for the outer class com.example.graph.spring.MongockBeanDefinitionRegistrar (currently missing).

@snicoll The code path involves Spring Data which invokes kotlinClass.getMembers() which requires the outer class hint, but maybe we could handle that defensively at Framework level. Any thoughts?

Unrelated, notice that an additional hint for io.mongock.runner.core.executor.MongockRunnerImpl is also needed to be able to detect the execute function and can't be, by design, guessed by the inference mechanism if I am not mistaken.

Comment From: snicoll

After chatting with Sébastien, we think that we should detect this case whenever we infer a reflection hint on a type. We also wonder if the same issue would happen if a non-static inner class was exposed that way in Java.