Thrown java.lang.NullPointerException when call method with generic type parameters from proxied class.

Case:

interface A <T> {
    suspend fun f1(a: T)
    suspend fun f2(a: T)
}

class SomeClass

@Component
class B : A<SomeClass> {

    override suspend fun f1(a: SomeClass) {}

    @Transactional
    override suspend fun f2(a: SomeClass) {}
}

@Service
class Service(
    private val component: A<SomeClass>
) {
    suspend fun test() {
        component.f2(SomeClass()) // call success
        component.f1(SomeClass()) // throw java.lang.NullPointerException
    }
}

Stack trace:

java.lang.NullPointerException: null at java.base/java.util.Objects.requireNonNull(Objects.java:233) at
org.springframework.core.CoroutinesUtils.invokeSuspendingFunction(CoroutinesUtils.java:111) at
org.springframework.aop.support.AopUtils$KotlinDelegate.invokeSuspendingFunction(AopUtils.java:376) at
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) at
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713)

Example solve: add bridge method resolve in https://github.com/spring-projects/spring-framework/blob/5edb4cb2c92eea9a6d1b5ef36c52d41b139c591d/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java#L720

...
if (chain.isEmpty()) {
    // We can skip creating a MethodInvocation: just invoke the target directly.
    // Note that the final invoker must be an InvokerInterceptor, so we know
    // it does nothing but a reflective operation on the target, and no hot
    // swapping or fancy proxying.
    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
    retVal = AopUtils.invokeJoinpointUsingReflection(target, BridgeMethodResolver.findBridgedMethod(method), argsToUse);
}
else {
    // We need to create a method invocation...
    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
...

Affects: \

Comment From: sdeleuze

Could you please check with the latest Boot 3.2 patch release available and provide a self contained reproducer (link to a repository or an attached archive)?

Comment From: cloudchamb3r

Same issue on Spring Boot Version 3.3

@SpringBootApplication
open class BuggyKotlinApplication

open class SomeClass()

open interface A<T> {
    suspend fun nonTransactional(a: T)
    suspend fun transactional(a: T)
}


@Component
open class B : A<SomeClass> {
    val logger = org.slf4j.LoggerFactory.getLogger(this::class.java)

    override suspend fun nonTransactional(a: SomeClass) {
        logger.info("f1 called")
    }

    @Transactional
    override suspend fun transactional(a: SomeClass) {
        logger.info("f2 called")
    }
}

@Service
class SomeService(
    private val component : A<SomeClass>
) {
    suspend fun test() {
        component.nonTransactional(SomeClass())
    }

    @Transactional
    suspend fun test2() {
        component.transactional(SomeClass())
    }
}

@RestController
class SomeController(
    private val service: SomeService
) {
    @GetMapping("/")
    suspend fun test(): String {
        service.test()
        return "test"
    }
    @GetMapping("/tx")
    suspend fun tx(): String {
        service.test2()
        return "tx"
    }

    @GetMapping("/both")
    suspend fun both(): String {
        service.test()
        service.test2()
        return "both"
    }

    @GetMapping("/both-rev")
    suspend fun bothRev(): String {
        service.test2()
        service.test()
        return "both-rev"
    }
}

fun main(args: Array<String>) {
    runApplication<BuggyKotlinApplication>(*args)
}

Comment From: gitdude1458

The issue seems to exists in all 3.2.x and 3.3.x branches. The latest version I could not reproduce this was 3.1.12. Notable change of Spring Boot 3.2.x is Kotlin 1.9.x (previously 1.8.x), however downgrading Kotlin back to 1.8.x does not seem to affect the issue in any way.

Notable change from 3.1.x to 3.2.0 was introduction of AOP support for Kotlin coroutines: https://github.com/spring-projects/spring-framework/issues/22462

My guess is that the previous support was somewhat limited / broken (it worked properly until the coroutine suspended first - which would break @Transactional for sure, but has been enough for aspects like @PreAuthorize that are fully executed before first suspend).

I understand the AOP support is somewhat limited as indicated by the issue, however for aspects like @PreAuthorize this is unfortunately regression.

Comment From: sdeleuze

Please provide a self-contained reproducer (link to a repository or an attached archive) as asked above.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: Vano2776

Test example: https://github.com/Vano2776/spring-aop-issue