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 does not work on kotlin components after migration 2.7.3 to 3.0.0 #33509

Closed
Azbesciak opened this issue Dec 11, 2022 · 14 comments
Labels
status: invalid An issue that we don't feel is valid

Comments

@Azbesciak
Copy link

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 #33471, but not sure if it is true. Maybe I miss something?
In spring-attic/spring-native#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?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 11, 2022
@philwebb
Copy link
Member

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.

@philwebb
Copy link
Member

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.

@philwebb philwebb added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged labels Dec 12, 2022
@Azbesciak
Copy link
Author

Azbesciak commented Dec 12, 2022

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?

@Azbesciak
Copy link
Author

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

@philwebb
Copy link
Member

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

@wilkinsona wilkinsona closed this as not planned Won't fix, can't repro, duplicate, stale Jan 3, 2023
@Azbesciak
Copy link
Author

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... :(

@wilkinsona
Copy link
Member

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.

@Azbesciak
Copy link
Author

@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
Azbesciak/spring-3-configuration-prop-issue@9b98b07

@wilkinsona
Copy link
Member

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 #33710.

@Azbesciak
Copy link
Author

@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... :/

@philwebb
Copy link
Member

philwebb commented Jan 7, 2023

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

@Azbesciak
Copy link
Author

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

@Azbesciak

This comment was marked as outdated.

@philwebb philwebb changed the title ConfigurationProperties does not work on kotlin components after migration 2.7.3 to 3.0.0 ConfigurationProperties does not work on kotlin components after migration 2.7.3 to 3.0.0 Mar 7, 2023
@Azbesciak
Copy link
Author

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 = ""
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

4 participants