Affects: Spring 6.0.11 / Spring Boot 3.1.2


For reproducing the issue, I highly recommend starting with the attached reproducer linked below.

Assume an application with the following components: - Spring Boot 3.1.2 - Spring JPA - Spring Security - Spring Web - Spring test - Spring Security test - H2 as a database (for tests, I don't think the DB affects this issue)

Let this application have a controller with an endpoint accepting a MultipartFile RequestParam that calls an @Transactional method. Add an entity with a String field (@Id) and a CrudRepository with a getBy<The string field>. Add an ApplicationListener<ApplicationReadyEvent> which calls the above method. Add a @SpringBootTest with two @Test methods, one of them annotated with @WithMockUser. These test methods can be empty.

Run the tests using native-image with mvn -PnativeTest test. An exception like the following should occur:

java.lang.IllegalStateException: Failed to load ApplicationContext for [AotMergedContextConfiguration@5207f8d0 testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, contextInitializerClass = io.github.danthe1st.spring_test.SpringTestApplicationTests__TestContext001_ApplicationContextInitializer, original = [WebMergedContextConfiguration@6b119ba0 testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, locations = [], classes = [io.github.danthe1st.spring_test.SpringTestApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@64e953ce, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@29866a5b, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@329bad59, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@24c2dbca, org.springframework.boot.test.context.SpringBootTestAnnotation@6c69f2e0], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]]
       org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:143)
       org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127)
       org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191)
       org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130)
       org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:241)
       [...]
     Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someController': Unexpected AOP exception
       org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:605)
       org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
       org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
       org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
       org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
       [...]
     Caused by: org.springframework.aop.framework.AopConfigException: Unexpected AOP exception
       org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:228)
       org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:155)
       org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
       org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.buildProxy(AbstractAutoProxyCreator.java:517)
       org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:464)
       [...]
     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: io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$1
       org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
       org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575)
       org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107)
       org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52)
       java.base@17.0.5/java.util.concurrent.FutureTask.run(FutureTask.java:264)
       [...]

Reproducer: https://github.com/danthe1st/spring-cglib-repro/actions/runs/5648996009 Build log with error: https://github.com/danthe1st/spring-cglib-repro/actions/runs/5648996009/job/15302392597

This issue is similar to #30937 but the error is different so I decided to report both issues.

Comment From: snicoll

same as https://github.com/spring-projects/spring-framework/issues/30937#issuecomment-1649166515

Comment From: danthe1st

When trying to reproduce the issue, it only happened with @Transactional (without it, I got to the other issue). MultipartFile is actually not required (I thought it was required because removing it caused the issue to disappear at some point in the process of creating the reproducer). I also added the enhancement plugin to the reproducer (this doesn't change anything and I have previously removed it so I can reduce the reproducer as much as possible). I assume that @Transactional causes the proxy class to be required/generated (at runtime).

The reproducer is now updated. The other issue is a bit different, I'll do similar steps there as well.

Comment From: snicoll

Thanks. The runtime expects io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$1 but only io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$0 has been generated. It looks like a second proxy is required that isn't discovered by AOT.

@jhoeller any idea how we could debug this?

Comment From: danthe1st

Output of the mvn test -PnativeTest command (for the future in case the GitHub Actions log expired): build.log

Comment From: danthe1st

Is there any chance in getting this finding out how this can be debugged/finding out why the second proxy would be necessary/why it isn't generated? To me, this seems like an issue that would be applicable in many cases.

Comment From: sbrannen

@danthe1st, what happens if you remove hibernate-enhance-maven-plugin from pom.xml?

Comment From: danthe1st

Originally, I didn't have the hibernate enhancer plugin in my reproducer and I just added it after https://github.com/spring-projects/spring-framework/issues/30939#issuecomment-1649380868 but I will update it.

Comment From: danthe1st

I have removed the plugin in a branch named cglib-no-hibernate-enhancer in my reproducer. You can find the build log here. As you can see, the error still occurs.

@sbrannen

Comment From: snicoll

@danthe1st to answer your question, we'll have to debug it to understand why the second proxy was not generated at build-time. It's high on my list and I hope to get to that next week.

Comment From: sbrannen

we'll have to debug it to understand why the second proxy was not generated at build-time. It's high on my list and I hope to get to that next week.

I believe this may actually be a duplicate of #31238 (see https://github.com/spring-projects/spring-framework/issues/31238#issuecomment-1722461876).

In any case, I think the underlying problem is that a new proxy class is generated for (what should be) the same key, which might imply that there is something wrong with the key we generate.

Comment From: danthe1st

I want to note that in contrast to #31238, this issue only seems to occur with Spring Boot 3.1.x and not with 3.0.x (if I remember correctly)

Comment From: sbrannen

I have confirmed that the local fix I have in place for #31238 allows the native image tests for the example project for this issue to pass using Spring Boot 3.1.2 and Spring Framework 6.0.13-SNAPSHOT.

In light of that, I am closing this issue as a:

  • Duplicate of #31238

Comment From: sbrannen

It turns out this issue was not only a duplicate of #31238 but also a:

  • Duplicate of #31050

Although I knew part of this issue was resolved in 6.0.13-SNAPSHOT, I wasn't sure what had actually caused the second attempt to create the @Transactional proxy class; whereas, in #31238 it was clearly due to the use of @DirtiesContext.

So I decided to debug the sample application for this issue further, and in doing so I noticed that #31050 was causing the first attempt to load the test's ApplicationContext to fail, and that's why the second @Test method resulted in a 2nd attempt to load the ApplicationContext, thereby displaying the same behavior as in #31238.

With that, the mystery is solved!