Skip to content

Fix thread-safety issue in DefaultModelValidator (#11618)#11734

Open
abhu85 wants to merge 1 commit intoapache:masterfrom
abhu85:gh-11618-thread-safe-validator
Open

Fix thread-safety issue in DefaultModelValidator (#11618)#11734
abhu85 wants to merge 1 commit intoapache:masterfrom
abhu85:gh-11618-thread-safe-validator

Conversation

@abhu85
Copy link

@abhu85 abhu85 commented Feb 23, 2026

Summary

  • Fix thread-safety issue where concurrent model validation causes ClassCastException
  • Aligns compat layer with Maven 4 implementation which already uses thread-safe collections
  • Adds regression test for concurrent validation

Problem

DefaultModelValidator is a @Singleton with a shared mutable HashSet (validIds). When multiple threads validate models concurrently (e.g., using breadth-first dependency collector with aether.dependencyCollector.impl=bf), concurrent read/write operations on the HashSet cause:

java.lang.ClassCastException: class java.util.HashMap$Node cannot
be cast to class java.util.HashMap$TreeNode
    at java.util.HashMap$TreeNode.putTreeVal(HashMap.java:2079)
    at java.util.HashMap.putVal(HashMap.java:634)
    at java.util.HashSet.add(HashSet.java:220)
    at o.a.m.model.validation.DefaultModelValidator.validateId(DefaultModelValidator.java:1145)

This affects users of:

  • Clojure tools-deps
  • Leiningen with aether.dependencyCollector.impl=bf
  • NetBeans IDE

Fix Approach

Replace new HashSet<>() with ConcurrentHashMap.newKeySet() - identical to the solution already implemented in Maven 4's maven-impl module (lines 296, 298).

Files Changed

File Change
compat/maven-model-builder/.../DefaultModelValidator.java Replace HashSetConcurrentHashMap.newKeySet() + explanatory comment
compat/maven-model-builder/.../DefaultModelValidatorTest.java Add concurrent validation test (10 threads × 100 models)

Test Plan

  • New test testConcurrentValidation() added
  • mvn -pl compat/maven-model-builder test
  • mvn -Prun-its verify (optional, if reviewer deems necessary)

Compatibility

  • Backwards compatible: ConcurrentHashMap.newKeySet() provides same Set interface
  • No behavioral change: Same validation logic, just thread-safe storage
  • Aligns with Maven 4: Mirrors the implementation in maven-impl module

Risk Assessment

Low risk:

  • Minimal diff (3 lines of production code including comment)
  • Same fix pattern already proven in Maven 4
  • Thread-safe collection is a drop-in replacement

Fixes #11618

The DefaultModelValidator class uses a shared HashSet (validIds) as
an instance field while being a @singleton. When multiple threads
validate models concurrently (e.g., using breadth-first dependency
collector with `aether.dependencyCollector.impl=bf`), concurrent
read/write operations on the HashSet cause ClassCastException:

  java.lang.ClassCastException: class java.util.HashMap$Node cannot
  be cast to class java.util.HashMap$TreeNode

This occurs when the HashMap underlying the HashSet resizes and
tree-ifies buckets while another thread is reading.

The fix aligns with the Maven 4 implementation (maven-impl module)
which already uses ConcurrentHashMap.newKeySet() for thread-safety.

Changes:
- Replace HashSet with ConcurrentHashMap.newKeySet() for validIds
- Add concurrent validation test to prevent regression

Fixes apache#11618
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DefaultModelValidator is not thread safe

1 participant