Application with Bean DSL and @ConstructorBinding
is unable to start due to org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pl.app.callme.CallMeProperties#0': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type pl.app.callme.CallMeProperties
even though both (or one of them) are present.
App:
@SpringBootApplication
@EnableConfigurationProperties(CallMeProperties::class)
@ConfigurationPropertiesScan(basePackages = ["pl.app"])
class CallmeApp
fun main(){
runApplication<CallmeApp> {
addInitializers(appBeans())
}
}
fun appBeans() = org.springframework.context.support.beans {
bean<CallMeProperties>()
bean {
TestRouter(ref()).testRouter()
}
}
@ConstructorBinding
@ConfigurationProperties(prefix = "callme")
data class CallMeProperties(
var property1: String,
var property2: String
)
Here's application.yml
:
callme:
property1: "Test1"
property2: "Test2cfg"
And build.gradle.kts
:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.3.1.RELEASE"
id("io.spring.dependency-management") version "1.0.9.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
kotlin("kapt") version "1.3.72"
}
group = "pl.app.callme"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
extra["springCloudVersion"] = "Hoxton.SR6"
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.cloud:spring-cloud-starter-config")
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")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("io.projectreactor:reactor-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
Full exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pl.app.callme.CallMeProperties#0': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type pl.app.callme.CallMeProperties
at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.validate(ConfigurationPropertiesBeanDefinitionValidator.java:66) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.postProcessBeanFactory(ConfigurationPropertiesBeanDefinitionValidator.java:45) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:291) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:175) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:62) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
at pl.app.callme.CallmeAppKt.main(CallmeApp.kt:36) ~[main/:na]
at pl.app.callme.CallmeAppKt.main(CallmeApp.kt) ~[main/:na]
App written in traditional way works just fine:
@SpringBootApplication
@ConfigurationPropertiesScan
class CallmeApp
fun main(){
runApplication<CallmeApp>()
}
@ConfigurationProperties(prefix = "callme")
@ConstructorBinding
class CallMeProperties(
val property1: String,
val property2: String
)
@RestController
@RequestMapping("/")
class CallmeController(
private val properties: CallMeProperties
) {
@GetMapping("/prop1")
suspend fun getProp1() = properties.property1
@GetMapping("/prop2")
suspend fun getProp2() = properties.property2
}
application.yml
:
callme:
property1: "Test1"
property2: "Test2cfg"
Comment From: piotr-karon
Update.
Removing
bean<CallmeProperties>()
from beans definition resolves the problem.
So the problem is the misleading exception message.
Comment From: snicoll
So the problem is the misleading exception message.
I am not sure what is misleading here. One one hand you've enabled component scanning for @ConfigurationProperties
and on the other hand you've defined the bean yourself. That registers a bean for that particular type twice.
App written in traditional way works just fine:
It's not the same thing, is it? In this version you're not defining the bean yourself.
Comment From: piotr-karon
Well, I think I once got error indicating that I redeclared bean (but I may be wrong) so I would say this could be clearer.
What I wanted to use in first place was @ConstructorBinding
annotation which requires @EnableConfigurationProperties
or @ConfigurationPropertiesScan
which requires @ConfigurationProperties
which led to above situation. I see now that my intention was wrong.
So, maybe I should give up on @ConstructorBinding
and use @Value
annotations? This works as expected, but seems not so clean:
@SpringBootApplication
class CallmeApp
fun main(){
runApplication<CallmeApp> {
addInitializers(appBeans())
}
}
fun appBeans() = org.springframework.context.support.beans {
bean<CallMeProperties>()
bean {
TestRouter(ref()).testRouter()
}
}
class CallMeProperties(
@Value("\${app.property1}") val property1: String,
@Value("\${app.property2}") val property2: String
)
Is this correct?
Comment From: snicoll
Is this correct?
Not really no. That sounds overcomplicated to what you had previously.
What I wanted to use in first place was @ConstructorBinding annotation which requires @EnableConfigurationProperties or @ConfigurationPropertiesScan which requires @ConfigurationProperties which led to above situation. I see now that my intention was wrong.
I have no idea how this led to the above situation. If you @EnableConfigurationProperties
then you provide the class and we register a bean (so you don't have to). If you use @ConfigurationPropertiesScan
then the class is scanned (so again, you don't have to register it). I don't understand why your @ConstructorBinding
example is so different than your "traditional way". It does not have to be that way.
This works just fine and is very similar to what you call "traditional way".
@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
@ConstructorBinding
@ConfigurationProperties(prefix = "callme")
data class CallMeProperties(
var property1: String,
var property2: String
)
If you have more questions, please follow-up on StackOverflow or chat with the community on Gitter.
Comment From: wilkinsona
The changes made for #20798 mean that the error message that is produced has changed, hopefully for the better. With the latest snapshots, the failure looks like this:
***************************
APPLICATION FAILED TO START
***************************
Description:
CallMeProperties is annotated with @ConstructorBinding but it is defined as a regular bean which caused dependency injection to fail.
Action:
Update your configuration so that CallMeProperties is defined via @ConfigurationPropertiesScan or @EnableConfigurationProperties.