Kotlin value classes currently cannot be used in configuration bindings in Spring Boot 2.6.6.

This is a repro case:

package com.example

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

fun main(args: Array<String>) {
    System.setProperty("SPRING_APPLICATION_JSON", "{ \"predicate\": \"C\" }")
    runApplication<SpringTest>(*args)
}

@SpringBootApplication
@ConfigurationPropertiesScan(basePackages = ["com.example"])
class SpringTest {

    @Bean
    fun someBean(config: PredicateConfiguration): String {
        println("predicate: ${config.predicate.value}")
        return ""
    }

}

@JvmInline
value class Predicate(val value: Char)

@ConfigurationProperties
@ConstructorBinding
data class PredicateConfiguration(val predicate: Predicate)

It fails with the following error message:

Failed to bind properties under '' to com.example.PredicateConfiguration:

    Reason: java.lang.IllegalArgumentException: object is not an instance of declaring class

Comment From: wilkinsona

Thanks for the report. The problem can be reproduced without Spring Boot:

package com.example.gh31398

import org.springframework.beans.BeanUtils

class Gh31398Application

fun main(args: Array<String>) {
    val constructor = PredicateConfiguration::class.java.constructors[0]
    val args = arrayOf(Character.valueOf('c'))
    BeanUtils.instantiateClass(constructor, *args)
}

@JvmInline
value class Predicate(val value: Char)

data class PredicateConfiguration(val predicate: Predicate)
Exception in thread "main" org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.gh31398.PredicateConfiguration]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221)
    at com.example.gh31398.Gh31398ApplicationKt.main(Gh31398Application.kt:10)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
    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:566)
    at kotlin.reflect.jvm.internal.calls.InlineClassAwareCaller.call(InlineClassAwareCaller.kt:134)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:159)
    at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
    at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:892)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:196)
    ... 1 more

I'm not sure if this is something that can be addressed in Framework's support for instantiating Kotlin types or if it's a bug in Kotlin's reflection support.

We'll transfer this to the Framework team so that they can take a look.

Comment From: sdeleuze

I don't think we support Kotlin inline classes yet in Spring portfolio as such support is not straightforward from a JVM bytecode reflection POV and we lack a global abstraction for Kotlin reflection. We had related discussion with the Spring Data team that shown it can be tricky topic even if this specific case is maybe doable without too much trouble.

Maybe we should at least document at Framework level it is not supported yet. I will create a related documentation issue.

Comment From: aseemsavio

Hello team - can support for Kotlin inline classes be expected in the near future? :)

Comment From: sdeleuze

No promise, but let's try to tackle that as part of 6.1.0-M3. See related https://github.com/spring-projects/spring-data-commons/pull/2866 Spring Data PR.

Comment From: sdeleuze

See related comments from @udalov in https://github.com/spring-projects/spring-data-commons/pull/2866#pullrequestreview-1533612845.

Comment From: ianbrandt

Does this enhancement mean Kotlin value classes can be used in 6.1+ to disambiguate @Bean types, including when used as type parameters?

For example:


@JvmInline
value class LogsPath(val value: Path)

@JvmInline
value class PrefsPath(val value: Path)

@Configuration
class ApplicationConfiguration {

    @Bean
    fun logsPathSupplier(): Supplier<LogsPath> = ...

    @Bean
    fun prefsPathSupplier() : Supplier<PrefsPath> = ...

    @Bean
    fun logService(logsPath: Supplier<LogsPath>) = ...

    @Bean
    fun prefsService(prefsPath: Supplier<PrefsPath>) = ...
}

If not, I'd file that as an additional feature request. I'm currently using @Named to disambiguate multiple such beans. Strongly-typed value class beans would be far preferred to stringly-typed bean names.

Also, just a heads up regarding some docs that may need to be adjusted for this change, https://docs.spring.io/spring-framework/reference/6.1/languages/kotlin/requirements.html currently has a blanket warning that, "Kotlin inline classes are not yet supported."

Comment From: ianbrandt

I tested it, and it looks like the answer to my above question is yes:

https://github.com/sdkotlin/sd-kotlin-spring-talks/commit/b6fef066474732ac3179e129e01124fc8c472301

I would foresee disambiguating multiple beans of the same type with value classes instead of bean names in most cases. Thanks!

Comment From: MrBuddyCasino

Oh nice, been waiting for this one, thanks for testing.

Comment From: ianbrandt

I stand corrected. It works for me with value classes as generic type parameters or function type return types (e.g. Supplier<MyValueClass>, () -> MyValueClass), even in Spring 6.0, but not as direct @Bean types as of 6.1.0-M5.

I filed a separate issue with reproducer: #31372.

Comment From: apankowski

This issue has been closed, but I have tried using Andy Wilkinson's reproducer and it failed for me exactly with the message mentioned by Andy.

I've tried it with:

  1. org.springframework:spring-beans:6.1.11
  2. Kotlin 1.9.25