Summary

A new change introduced in spring-context 6.2.0, which is shipped with springboot 3.4.0, creates StackOverflowError during spring-aot phase for java native applications, due to a newly added recursive processing behaviour.

Steps to reproduce

  • checkout minimum reproduciable project, https://github.com/jzhn/spring-aot-stackoverflow/tree/main
  • run mvn compile spring-boot:process-aot

Expected behaviour

above command completes successfully, java native related files are generated in ./target/spring-aot

Actual behaviour

spring-aot fails with Stackoverflow, with truncated stacktrace as below.

[INFO] --- spring-boot:3.4.0:process-aot (default-cli) @ demo ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.0)

2024-12-05T15:54:51.999-08:00  INFO 74371 --- [demo] [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 21.0.5 with PID 74371 (/Users/jzhang/workhome/spring-aot-stackoverflow/target/classes started by jzhang in /Users/jzhang/workhome/spring-aot-stackoverflow)
2024-12-05T15:54:52.000-08:00  INFO 74371 --- [demo] [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
Exception in thread "main" java.lang.StackOverflowError
        at java.base/java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:68)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281)
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:265)
        at org.springframework.core.SerializableTypeWrapper$TypeProxyInvocationHandler.invoke(SerializableTypeWrapper.java:209)
        at org.springframework.core.$Proxy6.getActualTypeArguments(Unknown Source)
        at org.springframework.core.ResolvableType.getGenerics(ResolvableType.java:809)
        at org.springframework.core.ResolvableType.getGeneric(ResolvableType.java:762)
        at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.lambda$processAheadOfTime$0(BeanValidationBeanRegistrationAotProcessor.java:162)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:728)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:707)
        at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.processAheadOfTime(BeanValidationBeanRegistrationAotProcessor.java:150)
        at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.lambda$processAheadOfTime$0(BeanValidationBeanRegistrationAotProcessor.java:170)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:728)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:707)
        at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.processAheadOfTime(BeanValidationBeanRegistrationAotProcessor.java:150)
        at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.lambda$processAheadOfTime$0(BeanValidationBeanRegistrationAotProcessor.java:170)

Notes

  • The version is reproduciable with Springboot 3.4.0, but it's okay with Springboot 3.3.6.
  • Suspected commit: https://github.com/spring-projects/spring-framework/commit/357dbc035438e6ca718fd03f9facd6a64b2545c5, where the recursive processing logic is added.

Comment From: jzhn

  • duplicate of #33936