Skip to content
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

@ConfigurationProperties recommendation for Kotlin doesn't produce metadata properties #42314

Closed
corneil opened this issue Sep 14, 2024 · 7 comments
Labels
status: duplicate A duplicate of another issue

Comments

@corneil
Copy link

corneil commented Sep 14, 2024

In the process of build a stream processor I discovered the recommended use of @ConfigurationProperties for kotlin doesn't work as expected.
My test-processor repo. https://github.com/corneil/test-processor

When building the Java version a file is produced named META-INF/spring-configuration-metadata.json containing:

{
  "groups": [
    {
      "name": "com.example.testprocessor",
      "type": "com.example.testprocessor.TestConfiguration",
      "sourceType": "com.example.testprocessor.TestConfiguration"
    }
  ],
  "properties": [
    {
      "name": "com.example.testprocessor.addition",
      "type": "java.lang.String",
      "description": "Will be added to name to make fullName",
      "sourceType": "com.example.testprocessor.TestConfiguration",
      "defaultValue": "N\/A"
    }
  ],
  "hints": []
}

The container image should have a label name org.springframework.boot.spring-configuration-metadata.json with all configuration properties and a label named org.springframework.cloud.dataflow.spring-configuration-metadata.json with the configuration properties from classes named in META-INF/dataflow-configuration-metadata.properties

{
  "groups": [
    { "name": "com.example.testprocessor", "type": "com.example.testprocessor.TestConfiguration", "sourceType": "com.example.testprocessor.TestConfiguration" },
    { "name": "com.example.testprocessor2", "type": "com.example.testprocessor.TestConfiguration2", "sourceType": "com.example.testprocessor.TestConfiguration2" }
  ],
  "properties": [
    {
      "name": "com.example.testprocessor.addition",
      "type": "java.lang.String",
      "description": "Will be added to name to make fullName",
      "sourceType": "com.example.testprocessor.TestConfiguration"
    }
  ]
}
  • When using the recommended data class the properties section doesn't contain the property com.example.testprocessor2.addition.
  • The defaultValue is missing from the property in the case where properies are detected.

Recommendation

@ConfigurationProperties(prefix = "com.example.testprocessor2")
data class TestConfiguration2(
    /**
     * Will be added to name to make fullName
     */
    val addition: String = "N/A"
)

Working version

@ConfigurationProperties(prefix = "com.example.testprocessor")
class TestConfiguration {
    /**
     * Will be added to name to make fullName
     */
    var addition: String = "N/A"
}

Reproduce

./build-only.sh
./build-images.sh
./inspect-images.sh
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 14, 2024
@philwebb
Copy link
Member

I think this one is probably a duplicate of #28046 or #16293. Do you agree @corneil?

@philwebb philwebb added the status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team label Sep 14, 2024
@fprochazka
Copy link
Contributor

I just found this issue after trying to figure out why the official guide doesn't work, when applied on current versions (Gradle 8.10, Kotlin 2.0.20, JDK 22, Spring Boot 3.3.3)

@fprochazka
Copy link
Contributor

fprochazka commented Sep 14, 2024

I've noticed the .java file that kapt generates in the gradle build directory and did some experiments:

when I set kapt.use.k2=true (in gradle.properties), and write the data class like this:

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.boot.context.properties.bind.DefaultValue

@ConfigurationProperties(prefix = QueryLoggingConfigurationProperties.PREFIX)
data class QueryLoggingConfigurationProperties @ConstructorBinding constructor(
    @DefaultValue("false")
    val enabled: Boolean = false,
) {

    companion object {
        const val PREFIX: String = "query.logging"
    }

}

then the spring-configuration-metadata.json is generated as expected.

Interestingly, when I mark the data class as @JvmRecord, the @ConstructorBinding was never propagated to the final .java file, only this specific incantation works.

@corneil
Copy link
Author

corneil commented Sep 14, 2024

I think this one is probably a duplicate of #28046 or #16293. Do you agree @corneil?

My test used a data class with a single constructor with a single property, it excludes #16293
Maybe #28046.

The important difference from those is the APT for the configuration properties with the 'normal' class missed the defaultValue.

@corneil
Copy link
Author

corneil commented Sep 14, 2024

I've noticed the .java file that kapt generates in the gradle build directory and did some experiments:

when I set kapt.use.k2=true (in gradle.properties), and write the data class like this:

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.boot.context.properties.bind.DefaultValue

@ConfigurationProperties(prefix = QueryLoggingConfigurationProperties.PREFIX)
data class QueryLoggingConfigurationProperties @ConstructorBinding constructor(
    @DefaultValue("false")
    val enabled: Boolean = false,
) {

    companion object {
        const val PREFIX: String = "query.logging"
    }

}

then the spring-configuration-metadata.json is generated as expected.

Interestingly, when I mark the data class as @JvmRecord, the @ConstructorBinding was never propagated to the final .java file, only this specific incantation works.

Adding a @DefaultValue is good news. I would prefer not doing that if the annotation is not required for Java.

@fprochazka
Copy link
Contributor

fprochazka commented Sep 14, 2024

After checking further, it seems that the @DefaultValue is not needed, the part that makes is work is using k2 and @ConstructorBinding constructor(

the k2 compiler for some reason generates multiple constructors in the .java file, and IMHO that's what confuses the processor. Adding the @ConstructorBinding probably forces it to focus on the correct constructor

@philwebb
Copy link
Member

My test used a data class with a single constructor with a single property, it excludes #16293

Although your Kolin class declares a single constructor, the generated bytecode has two. Here's the output from javap

$ javap -s com.example.testprocessor.TestConfiguration2

Compiled from "TestProcessor.kt"
public final class com.example.testprocessor.TestConfiguration2 {
  public com.example.testprocessor.TestConfiguration2(java.lang.String);
    descriptor: (Ljava/lang/String;)V

  public com.example.testprocessor.TestConfiguration2(java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker);
    descriptor: (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V

  public final java.lang.String getAddition();
    descriptor: ()Ljava/lang/String;

  public com.example.testprocessor.TestConfiguration2();
    descriptor: ()V
}

I think that makes this one a duplicate of #16293

@philwebb philwebb closed this as not planned Won't fix, can't repro, duplicate, stale Sep 15, 2024
@philwebb philwebb added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team labels Sep 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

4 participants