What

Spring boot uses AOT processing to generate required reflect/proxy/resource configs as well as CGLIB proxy classes in compile time. For beans defined with interface types, CGLIB proxy is usually unnecessary so process-aot job does not generate CGLIB proxy.

When org.springframework.data:spring-data-mongodb is imported, java native projects with repository beans defined with interface types will encounter exception in runtime about unexpected CGLIB usage for the repository class.

Update: upon further investigation, just having org.springframework:spring-tx is enough to trigger the issue. It's not directly caused by spring-data-mongodb.

Minimum reproduciable project

https://github.com/jzhn/springNativeBugSpringDataMongoDB. Please refer to its README for steps to reproduce, expected behaviour and actual behaviour.

Versions

  • JDK: 21.0.2-graalce
  • Spring-Boot: 3.4.2 (latest to date)
  • MacOS 15.2 on ARM64 mac

Comment From: snicoll

@jzhn thanks for the report and the reproducer. Here is the stacktrace:

2025-01-28T10:19:50.461+01:00  WARN 27662 --- [demo] [           main] o.s.c.support.GenericApplicationContext  : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'myDao': Unexpected AOP exception
Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'myDao': Unexpected AOP exception
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:369)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:289)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:223)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:979)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1243)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1186)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:307)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1122)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1093)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1030)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)
    at com.example.demo.DemoApplication.main(DemoApplication.java:18)
    at java.base@21.0.5/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myDao': Unexpected AOP exception
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:608)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:307)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1631)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1519)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
    at org.springframework.beans.factory.support.RegisteredBean.resolveAutowiredArgument(RegisteredBean.java:253)
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:366)
    ... 23 more
Caused by: org.springframework.aop.framework.AopConfigException: Unexpected AOP exception
    at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:243)
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:167)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization(AbstractAdvisingBeanPostProcessor.java:127)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:439)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1815)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
    ... 34 more
Caused by: java.lang.UnsupportedOperationException: CGLIB runtime enhancement not supported on native image. Make sure to include a pre-generated class on the classpath instead: com.example.demo.MongoDao$$SpringCGLIB$$0
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107)
    at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52)
    at java.base@21.0.5/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:57)
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:130)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:317)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:562)
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:407)
    at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:62)
    at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:228)
    ... 40 more

FTR, I don't believe that #32609 is linked to this.

Comment From: snicoll

I didn't look carefully enough at the sample and missed the comment you added in the configuration file:

    // replace above with below to workaround the issue
    // since defining bean with concreate type would 
    // make process-aot generate the required cglib proxy  
    // @Bean
    // public MongoDao myDao() {
    //     return new MongoDao();
    // }

This is what you should be doing. If you expose a type that does not provide the capabilities that are needed at runtime, Spring AOT will not get a chance to prepare the object to honor them.

Comment From: jzhn

@snicoll thanks for the quick triage and apologies for missing the doc about returning precise bean types.

For my learning - is it possible to implement some sort of generic logic to generate and register the CGLIB bean types manually? The reason I'm asking that is my org is doing a massive conversion to java native + springboot. So if there's a chance that such logic can be handled centrally, it could save teams some efforts of updating all of their configurations. For example, if teams know a certain package, like com.foo.repository, will contain Repository beans that would be exposed via interface typed beans in configurations, then we can do a class path scan and create CGLIB proxies for all classes during spring AOT.