Spring boot version: 2.6.3.
There is the data class with @ConfigurationProperties and @ConstructorBinding. This class contains the field which is collection. There are several property sources (application.yml, application-dev1.yml) which initialize the first element of the collection. Binding for this element doesn't work correctly. Values for initialazation pulls only from one property source. Expected behavior is the same as for field of type of some nested class: merging values from all property sources.
Kotlin properties class
@ConfigurationProperties("tpp.test.root")
@ConstructorBinding
data class RootPropperties(
var rootField1: String = "",
var rootField2: String = "",
var nested: NestedProperties = NestedProperties(),
var nestedList: List<NestedListProperties> = listOf()
) {
data class NestedProperties(
var nestedField1: String = "",
var nestedField2: String = ""
)
@ConstructorBinding
data class NestedListProperties(
var nestedListField1: String = "",
var nestedListField2: String = ""
)
}
application.yml
tpp:
test:
root:
root-field1: default
nested:
nested-field1: default
nested-list:
- nested-list-field1: default
application-dev1.yml
tpp:
test:
root:
root-field2: dev1
nested:
nested-field2: dev1
nested-list:
- nested-list-field2: dev1
Test
@ActiveProfiles("dev1")
@SpringBootTest
internal class ConfigurationPropertiesTest {
@Autowired
lateinit var environment: Environment
@Autowired
lateinit var rootPropperties: RootPropperties
@Test
fun `configuration properties binding`() {
Assertions.assertEquals("default", rootPropperties.rootField1)
Assertions.assertEquals("dev1", rootPropperties.rootField2)
Assertions.assertEquals("default", rootPropperties.nested.nestedField1)
Assertions.assertEquals("dev1", rootPropperties.nested.nestedField2)
Assertions.assertTrue(rootPropperties.nestedList.isNotEmpty())
//org.opentest4j.AssertionFailedError:
//Expected :default
//Actual :
Assertions.assertEquals("default", rootPropperties.nestedList[0].nestedListField1)
Assertions.assertEquals("dev1", rootPropperties.nestedList[0].nestedListField2)
}
@Test
fun `environment binding`() {
Assertions.assertEquals("default", environment.getProperty("tpp.test.root.root-field1"))
Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.root-field2"))
Assertions.assertEquals("default", environment.getProperty("tpp.test.root.nested.nested-field1"))
Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.nested.nested-field2"))
Assertions.assertEquals("default", environment.getProperty("tpp.test.root.nested-list[0].nested-list-field1"))
Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.nested-list[0].nested-list-field2"))
}
}
The test with RootProperties failed on assertEquals("default", rootPropperties.nestedList[0].nestedListField1) because rootPropperties.nestedList[0].nestedListField1 has empty value. All other assertions tests pass sucessfully. The binding doesn't work correctly just for collection.
At the same time the test with Environment passed successfully. And Environment.getProperty("tpp.test.root.nested-list[0].nested-list-field1") resolves corrected value: "default".
Comment From: philwebb
This is intentional behavior and covered in this section of the reference documention. Earlier versions of Spring Boot used a different approach and it ended up being quite confusing and hard to tell where list elements were coming from.
One possible work-around would be to switch your List to a Map which has a different merge strategy. You will need to find something unique to use as the key.
Comment From: typik89
Thanks for the reply. are there plans to fix it in further versions? why we can't think about list as about map with index as key
Comment From: philwebb
There's no plans to fix it because we intentionally want to treat lists this way.