Skip to content

Add support for Kotlin inline/value classes in BeanUtils #28638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
MrBuddyCasino opened this issue Jun 15, 2022 · 10 comments
Closed

Add support for Kotlin inline/value classes in BeanUtils #28638

MrBuddyCasino opened this issue Jun 15, 2022 · 10 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement
Milestone

Comments

@MrBuddyCasino
Copy link

MrBuddyCasino commented Jun 15, 2022

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
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 15, 2022
@wilkinsona
Copy link
Member

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.

@wilkinsona wilkinsona changed the title Support Kotlin value classes for configuration binding BeanUtils cannot instantiate a Kotlin data class that uses a value class as a constructor parameter Jun 16, 2022
@bclozel bclozel transferred this issue from spring-projects/spring-boot Jun 16, 2022
@bclozel bclozel added the theme: kotlin An issue related to Kotlin support label Jun 16, 2022
@sdeleuze sdeleuze added this to the 6.x Backlog milestone Jun 16, 2022
@sdeleuze sdeleuze removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 16, 2022
@sdeleuze
Copy link
Contributor

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.

@sdeleuze sdeleuze changed the title BeanUtils cannot instantiate a Kotlin data class that uses a value class as a constructor parameter Add support for Kotlin inline/value classes in BeanUtils Jan 17, 2023
@sdeleuze sdeleuze added the status: pending-design-work Needs design work before any code can be developed label Jan 17, 2023
@aseemsavio
Copy link

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

@sdeleuze sdeleuze added the type: enhancement A general enhancement label Jul 5, 2023
@sdeleuze sdeleuze self-assigned this Jul 5, 2023
@sdeleuze sdeleuze modified the milestones: 6.x Backlog, 6.1.0-M3 Jul 5, 2023
@sdeleuze
Copy link
Contributor

sdeleuze commented Jul 5, 2023

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

@sdeleuze
Copy link
Contributor

sdeleuze commented Aug 1, 2023

See related comments from @udalov in spring-projects/spring-data-commons#2866 (review).

@sdeleuze sdeleuze added in: core Issues in core modules (aop, beans, core, context, expression) and removed status: pending-design-work Needs design work before any code can be developed labels Aug 9, 2023
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 9, 2023
This commit adds more Kotlin value classes tests with
primitive types to ensure testing unwrapped use case.

See spring-projectsgh-28638
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 9, 2023
@ianbrandt
Copy link
Contributor

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."

@ianbrandt
Copy link
Contributor

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

sdkotlin/sd-kotlin-spring-talks@b6fef06

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

@MrBuddyCasino
Copy link
Author

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

@ianbrandt
Copy link
Contributor

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.

@apankowski
Copy link

apankowski commented Aug 2, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

9 participants