Currently the Binder class has a "first wins" policy for binding lists. From the docs:

When lists are configured in more than one place, overriding works by replacing the entire list.

This policy has worked well, however, it also causes problems such as #41669 where users really want to merge lists together.

I think it would be a mistake to attempt to compose lists from elements in different property sources, however, we might be able to offer a feature where distinct lists could be merged together.

Proposal

When property values are defined, the property name could include an additional indicator that is used to determine how the values should be merged. The indicator would be (+) for addAll and (-) for removeAll:

This example shows an auto-configure exclude:

.properties

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
#---
spring.config.activate.on-profile: noredis
spring.autoconfigure.exclude(+)=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

.yaml

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
---
spring:
  config:
    activate:
      on-profile: noredis
  autoconfigure:
    exclude(+): org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

More complex examples are:

.properties

my.list-of-names(+)[0].first=Spring
my.list-of-names(+)[0].last=Boot
my.list-of-names(+)[1].first=Spring
my.list-of-names(+)[1].last=Framework

.yaml

my:
  list(-):
    - remove
    - stuff

Limitations

It will be hard to support this syntax with environment variables.

Comment From: philwebb

Perhaps without the brackets. And perhaps only + for now.

Comment From: tmoschou

I'ld be interested to know how this might work for collections of complex types (which might have sub-collections). E.g.

my-collection:
  - name: foo
    prop: x 
    sub-collection:
      - item1
      - item2
  - name: bar
    prop: y
    sub-collection:
      - item3
      - item4

Removing an item from my-collection or setting a property of an item, without knowing its index might be tricky. E.g

'my-collection[name == "bar"].prop': "z"

We generally in our team have an convention that if - a collection might need to be mutated and - the items in that collection have a natural identifier (such as a sub-property name),

then its probably better modelled as map using the sub-property as a key - and in most other cases we don't actually need to mutate existing collections and content with "first wins" policy.

my-collection:
  foo:
    prop: x 
    sub-collection:
      - item1
      - item2
  bar: 
    prop: y
    sub-collection:
      - item3
      - item4