Info:
Kotlin: 2.2.20
KSP: 2.2.20-2.0.3
Spring Boot: 3.5.5
JDK: 21
MRE Project
https://github.com/austinarbor/spring-kotlin-issue
Description
When upgrading Kotlin from 2.2.10 to 2.2.20, and KSP from 2.2.10-2.0.2 to 2.2.20-2.0.3, and no other changes, I am getting a new error on startup
(i tried downgrading ksp to 2.2.10-2.0.2 and the error remained, so it seems related to kotlin rather than ksp)
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'addressRepo' defined in file [/Users/aa/git/spring-kotlin-issue/build/classes/kotlin/main/dev/aga/repo/AddressRepo.class]: Unexpected AOP exception
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:614) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1221) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1187) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.10.jar:6.2.10]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.10.jar:6.2.10]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.5.jar:3.5.5]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.5.5.jar:3.5.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.5.jar:3.5.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.5.jar:3.5.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.5.5.jar:3.5.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.5.5.jar:3.5.5]
at dev.aga.MainKt.main(Main.kt:13) ~[main/:na]
Caused by: org.springframework.aop.framework.AopConfigException: Unexpected AOP exception
at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:244) ~[spring-aop-6.2.10.jar:6.2.10]
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:168) ~[spring-aop-6.2.10.jar:6.2.10]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-6.2.10.jar:6.2.10]
at org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization(AbstractAdvisingBeanPostProcessor.java:128) ~[spring-aop-6.2.10.jar:6.2.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:445) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1829) ~[spring-beans-6.2.11.jar:6.2.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) ~[spring-beans-6.2.11.jar:6.2.11]
... 17 common frames omitted
Caused by: java.lang.IllegalStateException: Unable to load cache item
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:75) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:129) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:321) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:562) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:407) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:62) ~[spring-aop-6.2.10.jar:6.2.10]
at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:229) ~[spring-aop-6.2.10.jar:6.2.10]
... 23 common frames omitted
Caused by: java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
dev/aga/repo/AddressRepo$$SpringCGLIB$$0.delete(I)J @67: invokevirtual
Reason:
Type integer (current frame, stack[2]) is not assignable to 'java/lang/Object'
Current Frame:
bci: @67
flags: { }
locals: { 'dev/aga/repo/AddressRepo$$SpringCGLIB$$0', integer }
stack: { 'org/springframework/cglib/proxy/MethodInterceptor', 'dev/aga/repo/AddressRepo$$SpringCGLIB$$0', integer }
Bytecode:
0000000: 2ab4 0035 59c7 000c 572a b800 392a b400
0000010: 3559 c600 2f2a b200 3b04 bd00 3d59 031b
0000020: bb00 3f5a 5fb7 0042 53b2 0044 b900 4a05
0000030: 0059 c700 0857 09a7 0009 c000 4cb6 0050
0000040: ad2a 1bb6 0033 ad
Stackmap Table:
same_locals_1_stack_item_frame(@17,Object[#70])
same_locals_1_stack_item_frame(@58,Object[#61])
same_locals_1_stack_item_frame(@64,Long)
same_locals_1_stack_item_frame(@65,Object[#70])
at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
at java.base/java.lang.Class.forName(Class.java:534) ~[na:na]
at java.base/java.lang.Class.forName(Class.java:513) ~[na:na]
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:581) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:375) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52) ~[spring-core-6.2.11.jar:6.2.11]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:57) ~[spring-core-6.2.11.jar:6.2.11]
... 30 common frames omitted
Comment From: sdeleuze
Let me ask to the Kotlin team if they think that's on their side or not.
Comment From: ilmirus
Hi! Kotlin Team here. The reason is change of default for JvmDefault - https://youtrack.jetbrains.com/issue/KTLC-269.
Previously, we did not generate default methods by default. Instead, we used DefaultImpls class. Now, we generate both default methods and DefaultImpls class. Looks like, Spring does support this mode - only one on them, not both, probably, because only one of them was used: DefaultImpls by Kotlin and default methods by Java. So, as a workaround, either disable default methods generation or DefaultImpls generation.
Add the following to build.gradle.kts
// Use DefaultImpls
kotlin {
compilerOptions {
jvmDefault.set(org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode.DISABLE)
}
}
or
// Use default methods
kotlin {
compilerOptions {
jvmDefault.set(org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode.NO_COMPATIBILITY)
}
}
Comment From: austinarbor
@ilmirus I'm a little confused, does that mean that every kotlin-spring project that uses interfaces will need to add these compiler options?
From the stacktrace it seems lke a spring issue -
Exception Details:
Location:
dev/aga/repo/AddressRepo$$SpringCGLIB$$0.delete(I)J @67: invokevirtual
Reason:
Type integer (current frame, stack[2]) is not assignable to 'java/lang/Object'
The type in the interface is a Long (not int), which is what I assumed to be causing the problem.
Comment From: sdeleuze
@austinarbor I have asked a feedback from the Kotlin team to understand the reason of the error, does not mean there won't be a Spring fix adapting to those changes.
Be also aware that Spring Boot 3.x and Spring Framework 6.x are not tested with Kotlin 2.x, Spring Boot 4.x and Spring Framework 7.x will have a Kotlin 2.2 baseline, to be seen if we backport the fix (dependending on how involved it will be).
Comment From: sdeleuze
I am scheduling the resolution of this issue for Spring Framework 7.0.0-RC1, we may backport to 6.2, but not sure yet.
Comment From: kunyavskiy
It looks like the initial source of the issue is Kotlin 2.2.20 generating slightly different code, which SpringCGLIB can't process. The source of change is https://youtrack.jetbrains.com/issue/KT-76667.
When you have a default method in an interface, the compiler generates an overriding method in a class. Before 2.2, this method was called in the $DefaultImpl class, but starting from 2.2, it was called directly in the interface method with the invokespecial instruction.
This method was always a bridge by its essence, but it didn't have the ACC_BRIDGE flag in the class file, which was fixed in the issue mentioned above.
Unfortunately, CGLIB has special processing for bridges that use invokespecial or invokeinterface instructions.
This special processing makes some assumptions that are not correct for this case. In particular, it assumes no boxing can happen, as Java doesn't allow overriding between generics and int; it requires using java.lang.Integer in this case.
But that's not true for Kotlin. Kotlin can have this kind of override, which would lead to boxing or unboxing in such a bridge (depending on the direction of change and position), which is completely legitimate from jvm class-file point of view. Moreover, it can also have primitives and value class boxing/unboxing.
I think the problem was always here, but a change in KT-76667 made the code where this happens much simpler. Before, you would need some obscure cases, like overriding an interface method with a generic method from a superclass. Now, you just need an interface default method using generics, with a primitive passed as a generic argument.
As a workaround, you can add explicit override to interface, which would call a super method, it stops code from crashing.