Using Boot 3.0.2, we see a behavior in which only properties injected through a constructor binding is generated as part of spring-configuration-metadata.json. Any other properties in the ConfigurationProperties class are omitted from it.

Let's say we have the following ConfigurationProperties class.

@ConfigurationProperties("test.properties")
public class TestProperties {

    private final MyData myData;

    private String custom;

    public TestProperties(MyData myData) {
        this.myData = myData;
    }

    public MyData getMyData() {
        return myData;
    }

    public String getCustom() {
        return custom;
    }

    public void setCustom(String custom) {
        this.custom = custom;
    }
}

and

record MyData(String data) {}

We see the following in the generated spring-configuration-metadata.json. The custom property in the above ConfigurationProperty class is not present.

{
  "groups": [
    {
      "name": "test.properties",
      "type": "com.example.demo.TestProperties",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "properties": [
    {
      "name": "test.properties.my-data",
      "type": "com.example.demo.MyData",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "hints": []
}

This issue is related to https://github.com/spring-cloud/spring-cloud-stream/issues/2640

Comment From: ghost

Hello sir , I am new to open source contribution. I already know java , my tech stacks & tools includes C, C++ , Python , Java, JavaScript , HTML , CSS , SQL , Bootstrap, ReactJS, ExpressJS, NodeJS & Git . I need a little help from your side to contribute to these amazing projects.

Comment From: philwebb

Thanks for the offer @itsarraj but the issue tracker isn't the best place to ask for advice. We have a lot of folks subscribed to issues and they will each get a notification. If you're interested in helping, take a look at the CONTRIBUTING.adoc file in the repository and keep an eye open for issues tagged with status: first-timers-only or status: ideal-for-contribution. If you have more questions please join us at gitter.im.

Comment From: wilkinsona

@sobychacko is your expectation that both test.properties.my-data.data and test.properties.custom will be documented at build time and bound at runtime? We don't support both constructor binding and JavaBean-style binding of the same class so this won't work. You'll either get test.properties.my-data.data through constructor binding or test.properties.custom through JavaBean-style binding but not both at the same time.

Comment From: sobychacko

@wilkinsona Thank you for the reply. When I spoke to @philwebb about this issue, I thought there might be a way to get around that limitation. My expectation was that both constructor-binding and JavaBean styles could co-exist. In the Kafka binder for Spring Cloud Stream, we have a ConfigurationProperties class that uses both approaches. It looks like we need to remove that constructor injection for KafkaProperties. cc @garyrussell

Comment From: wilkinsona

It looks like the constructor that consumes KafkaProperties predates Boot's support for constructor binding so it would be interesting to understand the original intent.

KafkaBinderConfigurationProperties is defined as a @Bean, injecting an instance of Boot's KafkaProperties. Those KafkaProperties aren't constructor bound and aren't part of the spring.cloud.stream.kafka.binder namespace so I don't think they should be documented as such.

As far as I can tell, the problem here is that the annotation processor that generates the metadata may incorrectly determine that constructor binding is going to be used when it's actually JavaBean binding that will be performed. @Autowired on the constructor should fix it but perhaps the annotation processor isn't taking that into account when determining how the class's properties will be bound.

Comment From: sobychacko

I tried the @Autowired approach for KafkaProperties, but I didn't notice any difference. We don't need to document all the individual properties in KafkaProperties under the spring.cloud.stream.kafka.binder namespace. What's happening is just the opposite now, i.e., all the relevant properties are missing since KafkaProperties uses constructor binding and thus eclipses all the other individual properties.

Comment From: wilkinsona

With the above TestProperties class, Boot 2.7 produces this metadata:

{
  "groups": [
    {
      "name": "test.properties",
      "type": "com.example.demo.TestProperties",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "properties": [
    {
      "name": "test.properties.custom",
      "type": "java.lang.String",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "hints": []
}

(The app also won't start as there are no MyData beans).

Boot 3.0 produces the following metadata:

{
  "groups": [
    {
      "name": "test.properties",
      "type": "com.example.demo.TestProperties",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "properties": [
    {
      "name": "test.properties.my-data",
      "type": "com.example.demo.MyData",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "hints": []
}

This is due to the improved @ConstructorBinding detection in 3.0:

If, however, you have a @ConfigurationProperties and you want to inject beans into the constructor rather than binding it, you’ll now need to add an @Autowired annotation.

While the constructor injection is manual in the @Bean method in Cloud Stream's Kafka Binder, that's effectively the situation here. Adding @Autowired to the constructor of TestProperties results in Boot 3.0.2 producing the following metadata:

{
  "groups": [
    {
      "name": "test.properties",
      "type": "com.example.demo.TestProperties",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "properties": [
    {
      "name": "test.properties.custom",
      "type": "java.lang.String",
      "sourceType": "com.example.demo.TestProperties"
    }
  ],
  "hints": []
}

Things seem to be working as they should and @Autowired has the expected effect on metadata generation. That matches the code which takes @Autowired into account when deducing the bind constructor.

@sobychacko Can you please double-check that @Autowired wasn't effective?

Comment From: sobychacko

Ah ok. I did see the same output that you got above for Boot 3.0. I will try the @Autowired approach once again. I might not have tried it properly. I will confirm for you.

Comment From: sobychacko

Adding @Autowired to the constructor indeed works now. I think I was adding the @Autowired to the constructor arg before, which didn't work. You can close this issue now. Sorry for the noise, and thanks for all the help!

Comment From: wilkinsona

Thanks, @sobychacko. I'm going to leave this open for a bit as I'd like to discuss things with the team to see if we can figure out a way to make @Autowired unnecessary. It isn't ideal that you need to add it to a constructor that won't actually be used for auto-wiring (as you're calling it manually from a @Bean method) just to make the metadata generation work correctly.

Comment From: philwebb

We're going to look at a way to signal that bean binding should be used that isn't @Autowired. We're considering an enum on @ConfigurationProperties.

Comment From: philwebb

I started looking at adding an enum to @ConfigurationProperties but it's quite involved for a patch release. It might be better to recommend that @ConfigurationProperties is used on the @Bean method rather than the class for now.

Comment From: wilkinsona

With thanks to @sobychacko for verifying, this has been fixed by #35564.