Spring Boot with Kotlin fails with "Parameter specified as non-null is null" when you specify default parameters for method arguments and these arguments are passed from class constructor and constructor arguments are private.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Repository
import javax.annotation.PostConstruct

data class Token(val value: String)

@Configuration
class TokenConfig {
    @Bean fun getToken() = Token("1952f7d5a300")
}

@Repository
// if you remove private, all works fine
class TokenRepo(private val defaultToken: Token) {
    fun getToken(token: Token = defaultToken) = token
}

@SpringBootApplication
class Application(private val tokenRepo: TokenRepo) {
    @PostConstruct
    fun start() {
        val token = tokenRepo.getToken()
        println("My token: $token")
    }
}

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

Stacktrace

Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.example.app.TokenRepo.getToken, parameter token
    at com.example.app.TokenRepo.getToken(Main.kt)
    at com.example.app.TokenRepo$$FastClassBySpringCGLIB$$edca23c2.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
    at com.example.app.TokenRepo$$EnhancerBySpringCGLIB$$ccfc06be.getToken(<generated>)
    at com.example.app.TokenRepo.getToken$default(Main.kt:52)
    at com.example.app.Application.start(Main.kt:59)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157)
    ... 23 common frames omitted

Spring Boot version: 2.2.0.M2 Kotlin version: 1.3.31

Comment From: shenliuyang

class TokenRepo( val defaultToken: Token) {
    fun getToken(token: Token = defaultToken) = token
}

remove private can fix it.

Comment From: bedla

I also have similar issuse with NPE when calling dependant bean. Suppose @AsyncEnabled is properly configured. We have two variants - Case 1 private val on field where first call fails on NPE exception thrown

http-nio-8080-exec-1 : callSync
-> thrown NPE

task-2 : callAsync
-> return OK
  • Case 2 protected open val on field where both calls are OK
http-nio-8080-exec-1 : callSync
task-1 : callSync -> task
-> return OK

task-2 : callAsync
-> return OK

Seems that it is because invalid CGLIB proxy is generated at first case.

open class AsyncClass(
        private val taskExecutor: TaskExecutor
//        protected open val taskExecutor: TaskExecutor
) {
    fun callSync() {
        logThread("callSync")
        taskExecutor.execute {
            logThread("callSync -> task")
        }
    }

    @Async
    open fun callAsync() {
        logThread("callAsync")
    }

    private fun logThread(message: String) {
        println("${Thread.currentThread().name} : $message")
    }
}

Comment From: shenliuyang

Hi. this problem is that. default value on JVM actually invoke synthetic method. Without private generated code is

public static Token getToken$default(TokenRepo var0, Token var1, int var2, Object var3) {
      if (var3 != null) {
         throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: getToken");
      } else {
         if ((var2 & 1) != 0) {
            var1 = var0.getDefaultToken();
         }

         return var0.getToken(var1);
      }
   }

with private generated code is

   // $FF: synthetic method
   public static Token getToken$default(TokenRepo var0, Token var1, int var2, Object var3) {
      if (var3 != null) {
         throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: getToken");
      } else {
         if ((var2 & 1) != 0) {
            var1 = var0.defaultToken;
         }

         return var0.getToken(var1);
      }
   }

Spring CGLIB proxy invoke like this (proxyInstance) -> (orignalInstance) when you invoke TokenRepo.getToken without parameter , actually invoke getToken$default(proxyInstance) so you got Null value. Finally U should remove all 'private' field when use it as default value. and controlled by spring Sorry for my poor English

Comment From: shenliuyang

@bedla add open to you method callSync will fix you problem. because spring cannot proxy final method. Use 'plugin.spring' plugin for (gradle, maven) also can solve it .

Comment From: bedla

@shenliuyang I already have kotlin compiler plugin for spring and it does not solve it. It works only with @Component on bean class that I have intentionally did not used.

My point with this issue is that you can change behavior of cglib proxy byt changing how property taskExecutor is declared.

It is similar like open on method you suggested and you are right it is about final modifier on callSync method.

Reason why it is "working" in second case is because then protected open is used Kotlin generated following bytecode (as you can see getTaskExecutor method is ready to be proxied by cglib). But in first case it is usual field access inside final method which is currently unsolvable by cglib.

    public final void callSync() {
        this.logThread("callSync");
        this.getTaskExecutor().execute((Runnable) (new Runnable() {
            public final void run() {
                com.example.kotlincglibopen.AsyncClass.this.logThread("callSync -> task");
            }
        }));
    }

    @NotNull
    protected TaskExecutor getTaskExecutor() {
        return this.taskExecutor;
    }

Comment From: shenliuyang

@bedla This problem also exist in java, if you define a final method use a private field. Create a custom Annotation annotate on AsyncClass and use allOpen { annotation("you.custom.Annotation") } all-open-plugin And i have a bit confusing, without any of (Component,Service,Controller,Repository) annotation on AsyncClass , Spring will do not any magic for it. Why it proxy by CGLIB.

Comment From: bedla

kotlin related issue https://youtrack.jetbrains.com/issue/KT-28586

Comment From: cypressious

Just got bit by this and removing private from the constructor parameter as recommended in https://github.com/spring-projects/spring-framework/issues/22948#issuecomment-493376050 is what helped.

This is really weird. The class is annotated with @Service and the kotlin-spring plugin is doing its job since everything else is working as intended.

Comment From: shenliuyang

@cypressious Hi there is no magic, kotlin-spring just make class 's func open that annotated @Service @Repository ext . In java if you declare a final method in you service, and use private field in this method. you will get NPE. default parameter in kotlin is managed by invoker, get private default parameter the only way is generate synthetic method.

Spring cglib proxy proxyInstance->realInstance. proxyInstance created by Unsafe. (did not initilizer any field, all you field is null include final field)

Time is fly , one year ago , my English bad every day. hahahaha

Comment From: sdeleuze

Hi, sorry for the delay. I can still reproduce with Spring Framework 6.0.4, I will discuss that with the team.

Comment From: sdeleuze

Can't reproduce anymore with Spring Boot 3.1.2 and Kotlin 1.8.22, so I close this issue. Please comment if still relevant and provide a repro Git repository or archive.