spring-boot version: 3.0.2

gradle file from example application:

plugins {
    id("org.springframework.boot") version "3.0.2"
    id("io.spring.dependency-management") version "1.1.0"
    id("org.graalvm.buildtools.native") version "0.9.18"
    kotlin("jvm") version "1.7.22"
    kotlin("plugin.spring") version "1.7.22"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("io.projectreactor:reactor-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Example application:

@SpringBootApplication
@EnableConfigurationProperties(
    AppProperties::class
)
class DemoApplication

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

@ConfigurationProperties("app.property")
data class AppProperties(
    var a: Boolean = true,
)


@RestController
@RequestMapping("/")
class TestController(val appProperty: AppProperties) {

    @GetMapping()
    fun hello(): String {
        return "Hello world: ${appProperty.a}"
    }

}

Exception that is thrown when command ./gradlew nativeRun is run:

Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError: This callable does not support a default call: public constructor AppProperties(a: kotlin.Boolean = ...) defined in com.example.demo.AppProperties[DeserializedClassConstructorDescriptor@7f41fb4b]
        at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:164) ~[demo:1.7.22-release-288(1.7.22)]
        at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112) ~[demo:1.7.22-release-288(1.7.22)]
        at org.springframework.beans.BeanUtils$KotlinDelegate.instantiateClass(BeanUtils.java:895) ~[na:na]
        at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:191) ~[na:na]
        at org.springframework.boot.context.properties.bind.ValueObjectBinder$ValueObject.instantiate(ValueObjectBinder.java:178) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.ValueObjectBinder.create(ValueObjectBinder.java:97) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.create(Binder.java:369) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.handleBindResult(Binder.java:358) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:344) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:332) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.bindOrCreate(Binder.java:324) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.bind.Binder.bindOrCreate(Binder.java:309) ~[demo:3.0.2]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bindOrCreate(ConfigurationPropertiesBinder.java:100) ~[demo:na]
        at org.springframework.boot.context.properties.ConstructorBound.from(ConstructorBound.java:43) ~[na:na]
        at com.example.demo.AppProperties__BeanDefinitions.getAppPropertiesInstance(AppProperties__BeanDefinitions.java:24) ~[na:na]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1225) ~[demo:6.0.4]
        ... 33 common frames omitted

But it works us expected if command ./gradlew bootRun is run.

Comment From: gzeskas

Found a possible work-around for this issue. Need to replace data class with class Example that works:

@ConfigurationProperties("app.property")
class AppProperties {
    var a: Boolean = true
}

Comment From: wilkinsona

Thanks for the reproducer.

Compilation of the AppProperties data class with a default value produces bytecode with two constructors:

public com.example.gh34157.AppProperties(boolean);
public com.example.gh34157.AppProperties(boolean, int, kotlin.jvm.internal.DefaultConstructorMarker);

The reflection config that's generated at build time only permits access to one of these constructors:

  {
    "name": "com.example.gh34157.AppProperties",
    "queryAllDeclaredMethods": true,
    "methods": [
      {
        "name": "<init>",
        "parameterTypes": [
          "boolean"
        ]
      }
    ]
  },

In the case of a Kotlin data class, BindableRuntimeHintsRegistrar needs to allow the AppProperties(boolean, int, kotlin.jvm.internal.DefaultConstructorMarker) constructor to be called as well.

@sdeleuze Does Kotlin Reflect allow the constructors that are associated with AppProperties(boolean) to be identified, or should we just allow invocation of all declared constructors in the case of a Kotlin data class?

Comment From: sdeleuze

I think you should allow the invocation of all declared constructors for Kotlin data classes.

Comment From: wilkinsona

Thank you, @sdeleuze.