Skip to content

Bind immutable collections and maps by default - #51853

Open
ABin-Huang wants to merge 1 commit into
spring-projects:mainfrom
ABin-Huang:27582-immutable-collection-binding
Open

ABin-Huang wants to merge 1 commit into
spring-projects:mainfrom
ABin-Huang:27582-immutable-collection-binding

Conversation

@ABin-Huang

Copy link
Copy Markdown

Summary

When binding to collection or map interface types (List, Set, SortedSet, Map, SortedMap), the bound result is now wrapped in an unmodifiable collection or map. This prevents accidental mutation of bound configuration state and aligns with the immutable configuration properties approach discussed in the issue.

Design decisions

  1. Interface types → immutable by default: Properties declared as List, Set, SortedSet, Map, or SortedMap receive unmodifiable instances.

  2. Concrete types → remain mutable: Properties declared as concrete mutable types (e.g. LinkedList, ArrayList, HashMap, TreeMap) remain mutable. The user has explicitly chosen a mutable implementation, and creating immutable subclasses for every concrete type is not practical.

  3. Existing values → preserve mutability: When an existing value is supplied via Bindable.withExistingValue(), its mutability is preserved. The user has provided their own instance and may rely on its mutability.

  4. Implementation location: The immutability wrapping happens in Binder.handleBindResult() after the final type conversion, ensuring all aggregate types (including Set which is converted from the internal List) are correctly handled.

Changes

  • Binder.java: Added makeImmutableIfNeeded() and helper methods to wrap bound collections/maps in unmodifiable instances when the target type is an interface.
  • CollectionBinderTests.java: Added tests for unmodifiable List, Set, concrete type mutability, and nested collections.
  • MapBinderTests.java: Added tests for unmodifiable Map and concrete type mutability.

Notes for reviewers

  • This is a behavioral change: code that mutates bound collections/maps declared as interface types will now throw UnsupportedOperationException. Users who need mutable collections should declare concrete types or provide an existing mutable value.
  • An opt-out mechanism (e.g. a @ConfigurationProperties attribute or system property) can be added if the team feels it's needed. I wanted to keep this PR focused on the core behavior change and get feedback on the approach first.

Closes gh-27582

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 24, 2026
@ABin-Huang
ABin-Huang force-pushed the 27582-immutable-collection-binding branch 2 times, most recently from 8f9fec8 to e43f48f Compare September 24, 2026 15:55
Bind collection and map interface types (List, Set, SortedSet, NavigableSet,
Collection, Map, SortedMap, NavigableMap) as unmodifiable instances by default.
Concrete mutable types (LinkedList, HashMap, Properties, etc.) remain mutable.

Add mutableCollections attribute to @ConfigurationProperties as an escape hatch
for users who need mutable binding. When set to true, bound collections and maps
remain mutable.

Closes spring-projectsgh-27582

Signed-off-by: ABin-Huang <2423244747@qq.com>
@ABin-Huang
ABin-Huang force-pushed the 27582-immutable-collection-binding branch from e43f48f to bec2f04 Compare September 24, 2026 16:48
@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Sep 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

for: team-meeting An issue we'd like to discuss as a team to make progress status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@ConfigurationProperties creates mutable collections, even on immutable classes

3 participants