Skip to content

Honor Map.hashCode() contract in MultiValueMap adapters - #37474

Open
lejuho wants to merge 1 commit into
spring-projects:mainfrom
lejuho:multi-value-map-adapter-hashcode
Open

lejuho wants to merge 1 commit into
spring-projects:mainfrom
lejuho:multi-value-map-adapter-hashcode

Conversation

@lejuho

@lejuho lejuho commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Overview

Fixes #37469.

SingleToMultiValueMapAdapter (returned by MultiValueMap.fromSingleValue()) exposes singleton-list values through entrySet() and compares those in equals(), but hashCode() delegated to the underlying single-value map. As a result, the adapter could be equal to a regular Map<K, List<V>> with the same entries while reporting a different hash code, breaking the Map.hashCode() contract and lookups in hash-based collections.

The symmetric MultiToSingleValueMapAdapter (returned by MultiValueMap.asSingleValueMap()) has the same problem in the opposite direction: it exposes the first value per key, but hashCode() delegated to the underlying multi-value map.

Changes

  • SingleToMultiValueMapAdapter.hashCode() now sums the hash codes of its adapted entries.
  • MultiToSingleValueMapAdapter.hashCode() now sums hash(key) ^ hash(firstValue) for each entry, using the same first-value adaptation as get(), so keys mapped to an empty list contribute like a null value instead of failing.

Testing

  • SingleToMultiValueMapAdapterTests: equal/hashCode/HashSet lookup against a regular map, including a null value.
  • MultiValueMapTests: asSingleValueMap() equal/hashCode/HashSet lookup against a regular map, and hashCode with an empty value list, for all maps under test.
./gradlew :spring-core:test --tests 'org.springframework.util.*'
./gradlew :spring-core:checkstyleMain :spring-core:checkstyleTest

Result: BUILD SUCCESSFUL. The new tests fail without the fix (12 failures).

SingleToMultiValueMapAdapter and MultiToSingleValueMapAdapter expose
adapted entry values (singleton lists and first values, respectively)
through entrySet() and equals(), but delegated hashCode() to the target
map whose entry values differ. As a consequence, an adapter could be
equal to a regular map while having a different hash code.

This commit computes hashCode() from the adapted entries, as defined by
the Map.hashCode() contract.

Closes spring-projectsgh-37469

Signed-off-by: lejuho <zedd1324@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 14, 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

status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MultiValueMap.fromSingleValue violates the equals/hashCode contract

2 participants