Hello, I wanted to see what problems may occur after migration from 2.7.3 to 3.0.0 - my target is to prepare the native application. So, I have read that there may be some issues with @ConditionalOnProperty or @ConditionalOnBean - I still do not understand why, but ok - wanted to check that. I created the example project, generated from the spring initializr, you may find it there (spring boot version 2.7.3, to see the issue you need to switch to 3.0.0) https://github.com/Azbesciak/spring-3-configuration-prop-issue

the problem is with the following, which works in 2.7.3

@ConfigurationProperties("my-service")
@ConditionalOnProperty("my-service", havingValue = "c")
@Component
data class SomeBean(var customValue: String = "not set")

but does not in 3.0.0 java.lang.IllegalStateException: Cannot bind @ConfigurationProperties for bean 'someBean'. Ensure that @ConstructorBinding has not been applied to regular bean

I initially tried with the @Bean in service, like below


@ConfigurationProperties("my-service")
data class SomeBean(var customValue: String = "not set")

@Configuration
class MyConfig {
    @Bean
    @ConditionalOnProperty("my-service", havingValue = "c")
    fun someBean() = SomeBean()
}

But it failed without error, the property was also not set.

BTW I was under the impression that this should work

data class SomeBean(var customValue: String = "not set")

@Configuration
@EnableConfigurationProperties
class MyConfig {
    @Bean
    @ConditionalOnProperty("my-service", havingValue = "c")
    @ConfigurationProperties("my-service")
    fun someBean() = SomeBean()
}

but it does only when I change the property name in @ConfigurationProperties to be outside the scope of my-service (my-service.value or xyz would work - in 2.7.3, in 3.0.0 does not)

I saw https://github.com/spring-projects/spring-boot/issues/33471, but not sure if it is true. Maybe I miss something? In https://github.com/spring-projects-experimental/spring-native/issues/1679 you made a statement, based on which I thought that in 3.0.0 everything is going to work without any effort. So... is it a bug, or a feature?

Comment From: philwebb

Spring Boot 3.0 removed the need for adding @ConstructorBinding annotations but as a result it no longer knows how you want SomeBean to be dealt with. It doesn't know if you are looking to create a regular bean or a configuration properties instance. You can read about his here: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes#improved-constructorbinding-detection.

If you really want the class to be a bean, you need to make sure there is a @Autowired constructor:

@ConfigurationProperties("my-service")
@ConditionalOnProperty("my-service", havingValue = "c")
@Component
data class SomeBean(var customValue: String = "not set") {

    @Autowired
    constructor() : this("default")

}

Or you could just drop the customValue parameter so constructor binding isn't attempted.

Comment From: philwebb

As a related note, you should not be using @ConditionalOnBean on regular beans. That condition can only be applied to auto-configurations. Please see the javadoc:

The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.

Comment From: Azbesciak

From that doc entry...

For most users

Really? Examples on Baeldung also showed it like that.

Or you could just drop the customValue parameter so constructor binding isn't attempted.

The problem is that this class is designed for parameter holding.

It was also told that this change is an improvement. I do not see it. What was wrong with the previous approach? Why is it better now? And why do I feel lost?

Let us say that I do not it to be a real bean - I mean a Component or service. What with a @Bean approach then, and why does not it work then? I mean this one

data class SomeBean(var customValue: String = "not set")

@Configuration
@EnableConfigurationProperties
class MyConfig {
    @Bean
    @ConditionalOnProperty("my-service", havingValue = "c")
    @ConfigurationProperties("xyz")
    fun someBean() = SomeBean()
}

And why there is no exception if it worked previously?

Comment From: Azbesciak

@philwebb could you please see the comment above? And also, is this approach more or less final, or still possible to change?

Comment From: philwebb

@Azbesciak I'm afraid I don't really understand the sample above so perhaps we can take a step back. What is it that you are trying to achieve by having customValue defined with a default of "not set"? Are you wanting to be able to bind xyz.custom-value from your application.properties to that bean?

If so, I'm afraid your approach won't work. If you want to create someBean from MyConfig then constructor binding cannot be used. You're taking control of creating the someBean instance. When @ConfigurationProperties is used on a @Bean method, binding occurs after the instance has been created. In other words, you need setters rather than a data class.

Comment From: Azbesciak

Are you wanting to be able to bind xyz.custom-value from your application.properties to that bean?

Yes, exactly

When @ConfigurationProperties is used on a @Bean method, binding occurs after the instance has been created

but it worked in 2.7... :(

Comment From: wilkinsona

Nothing has changed in this regard in Spring Boot 3.0 – it has never been possible to use constructor binding when application code is creating the instance. You need to let Spring Boot create the instance for you if you want to use constructor binding.

I don't think we really understand what you're trying to do and what worked in 2.7 and does not work in 3.0. To help us to understand, please create a minimal sample that works with 2.7.x and then fails when upgraded to 3.0.x. You can share such a sample with us by pushing it to a separate repository on GitHub or zipping it up and attaching it to this issue.

Comment From: Azbesciak

@wilkinsona I was under the impression I did it at the very beginning and described it in the issue - please look at it> If the provided description is not sufficient, I added a new branch and the exact code about @ConfigurationProperties and Bean, and that it worked in 2.7, and does not in 3.0. It is available on the branch config-prop-bean, below are changes https://github.com/Azbesciak/spring-3-configuration-prop-issue/commit/9b98b07fa2684e5e6dbb1c02dbb78ea2f958fa1b

Comment From: wilkinsona

Sorry, I should have been clearer about what I was looking for. It was a complete example of the @Bean-based approach that I wanted to see. Thank you for providing one. It isn't working due to https://github.com/spring-projects/spring-boot/issues/33710.

Comment From: Azbesciak

@wilkinsona Thank you - is it going to be fixed, or it was a bug previously as I would interpret from @philwebb response? As I mentioned some of these were published even on Baeldung so the problem scope may be quite wide... and it does not help that it fails silently.

Anyway, I would need to change around 70% of my @ConfigurationProperties beans if it would not be provided... :/

Comment From: philwebb

@Azbesciak The sample in #33710 helped me understand the issue better and I've just pushed a fix. 🤞it will also solve your problem. If you want to give it a try, you can try the 3.0.2-SNAPSHOT release.

Comment From: Azbesciak

@philwebb Thank you a lot, I also see that the initially reported issue is fixed! Works as expected, thank you for your time and understanding.

Comment From: Azbesciak

I noticed that when I run gradle bootBuildImage on that project it fails in runtime with the following error

Error creating bean with name 'myServiceC': Unsatisfied dependency expressed through constructor parameter 0: Error cr eating bean with name 'someBean': Runtime reflection is not supported for public void com.example.demo.SomeBean.setCustomValue(java.lang.String)

My question is: do I need to add hints for every bean created like this? Even if this is @ConfigurationProperties annotated. Based on https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.advanced I would expect that I do not have to, but it looks like I do.

Given sample fails for me without hint:

@Configuration(proxyBeanMethods = false)
class ConfigProvider {
    @Bean
    fun appSettings() = AppSettings()
}

@ConfigurationProperties(prefix = "app")
data class AppSettings(
    var appName: String = "",
    var packageName: String = "",
    var serviceAccountFilePath: String = "",
    var serviceScope: String = "",
)

Comment From: Azbesciak

Did the issue come back? I bumped up from 3.0.2 to 3.1.1 and...

Cannot bind @ConfigurationProperties for bean 'externalJwtTokenParserConfig'. Ensure that @ConstructorBinding has not been applied to regular bean


@Component
@ConditionalOnProperty("auth.jwt.external.enabled", havingValue = "true")
@ConfigurationProperties("auth.jwt.external")
data class ExternalJwtTokenParserConfig(
    var service: ExternalJwtPaths = ExternalJwtPaths(),
)

data class ExternalJwtPaths(
    var uriRoot: String = "",
    var path: String = ""
)