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:
org.springframework:spring-beans:6.1.11
- Kotlin 1.9.25