Affects: 6.1.2
When creating a project using Spring Boot and Spring Security with an (empty) test, running that test with native-image
fails with:
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'mvcHandlerMappingIntrospectorRequestTransformer' defined in null: Cannot register bean definition [Root bean: class [org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] for bean 'mvcHandlerMappingIntrospectorRequestTransformer' since there is already [Root bean: class [org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] bound.
Steps to reproduce
- Create a project using Spring Boot with
native-image
support and the following dependencies: spring-boot-starter-security
spring-boot-starter-web
spring-boot-starter-test
spring-security-test
- Add a
@SpringBootTest
class with a@Test
method - Run it with
mvn clean test -PnativeTest
@SpringBootApplication
public class SpringTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTestApplication.class, args);
}
}
@SpringBootTest
class SpringTestApplicationTests {
@Test
void testSomething() throws Exception {
}
}
A full reproducer can be found here (CI logs here).
Expected behavior
The test should run successfully both in JIT and native mode.
Actual behavior
When running the test with native-image
, it fails as follows:
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'mvcHandlerMappingIntrospectorRequestTransformer' could not be registered. A bean with that name has already been defined and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
2023-12-22T10:24:03.009Z WARN 2117 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener] to prepare test instance [io.github.danthe1st.spring_test.SpringTestApplicationTests@48da8c30]
java.lang.IllegalStateException: Failed to load ApplicationContext for [AotMergedContextConfiguration@4b6d6a7c testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, contextInitializerClass = io.github.danthe1st.spring_test.SpringTestApplicationTests__TestContext001_ApplicationContextInitializer, original = [WebMergedContextConfiguration@43fd1d08 testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, locations = [], classes = [io.github.danthe1st.spring_test.SpringTestApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@452e5e70, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@77d6324, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@7c0e5467, 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.WebDriverContextCustomizer@67e22fc2, org.springframework.boot.test.context.SpringBootTestAnnotation@c0804e63], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]]
Positive matches:
-----------------
None
Negative matches:
-----------------
None
Exclusions:
-----------
None
Unconditional classes:
----------------------
None
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180) ~[native-tests:6.1.2]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) ~[na:na]
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191) ~[native-tests:6.1.2]
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130) ~[native-tests:6.1.2]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) ~[native-tests:6.1.2]
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:163) ~[native-tests:6.1.2]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:378) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:383) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:378) ~[native-tests:5.10.1]
at java.base@17.0.9/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
at java.base@17.0.9/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
at java.base@17.0.9/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) ~[na:na]
at java.base@17.0.9/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[native-tests:na]
at java.base@17.0.9/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[native-tests:na]
at java.base@17.0.9/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) ~[na:na]
at java.base@17.0.9/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735) ~[native-tests:na]
at java.base@17.0.9/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) ~[native-tests:na]
at java.base@17.0.9/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) ~[na:na]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:377) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:290) ~[native-tests:5.10.1]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:289) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:279) ~[native-tests:5.10.1]
at java.base@17.0.9/java.util.Optional.orElseGet(Optional.java:364) ~[native-tests:na]
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:278) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:106) ~[native-tests:5.10.1]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:105) ~[native-tests:5.10.1]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:69) ~[native-tests:5.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123) ~[na:na]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123) ~[na:na]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90) ~[na:na]
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511) ~[native-tests:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[na:na]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[na:na]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[na:na]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[na:na]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[na:na]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[na:na]
at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511) ~[native-tests:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[na:na]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[na:na]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[na:na]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[na:na]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[native-tests:1.10.1]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[na:na]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[na:na]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[na:na]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[native-tests:1.10.1]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) ~[na:na]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) ~[na:na]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) ~[na:na]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) ~[na:na]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) ~[na:na]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) ~[na:na]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) ~[na:na]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:94) ~[na:na]
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:52) ~[native-tests:1.10.1]
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:70) ~[na:na]
at org.graalvm.junit.platform.NativeImageJUnitLauncher.execute(NativeImageJUnitLauncher.java:74) ~[na:na]
at org.graalvm.junit.platform.NativeImageJUnitLauncher.main(NativeImageJUnitLauncher.java:129) ~[na:na]
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'mvcHandlerMappingIntrospectorRequestTransformer' defined in null: Cannot register bean definition [Root bean: class [org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] for bean 'mvcHandlerMappingIntrospectorRequestTransformer' since there is already [Root bean: class [org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] bound.
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:1017) ~[native-tests:6.1.2]
at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$1.postProcessBeanDefinitionRegistry(WebMvcSecurityConfiguration.java:141) ~[na:na]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) ~[na:na]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:148) ~[na:na]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:789) ~[native-tests:6.1.2]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:606) ~[native-tests:6.1.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) ~[native-tests:3.2.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:464) ~[native-tests:3.2.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[native-tests:3.2.1]
at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) ~[native-tests:3.2.1]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[native-tests:6.1.2]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[native-tests:6.1.2]
at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1458) ~[native-tests:3.2.1]
at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:552) ~[na:na]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) ~[native-tests:3.2.1]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContextForAotRuntime(SpringBootContextLoader.java:119) ~[native-tests:3.2.1]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInAotMode(DefaultCacheAwareContextLoaderDelegate.java:255) ~[native-tests:6.1.2]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:149) ~[native-tests:6.1.2]
... 68 common frames omitted
io.github.danthe1st.spring_test.SpringTestApplicationTests > testSomething() FAILED
Comment From: snicoll
Thanks for the report. This is a Spring Security concern that registers HandlerMappingIntrospectorRequestTransformer
as part of a BeanDefinitionRegistryPostProcessor
that is not AOT-aware. As a result, the bean is contributed to the AOT context, which then produces an optimization for it and the post-processor is called again at runtime to create the same bean.
Can you please move the report to the Spring Security project? Unfortunately, I can't transfer it.
Comment From: danthe1st
It seems like this was already reported in https://github.com/spring-projects/spring-security/issues/14362 so I won't recreate it.