Problem
With spring-modulith-junit on the classpath and JUnit 5 parallel class execution
enabled, test classes fail before executing with:
org.junit.jupiter.engine.execution.ConditionEvaluationException:
Failed to evaluate condition [org.springframework.modulith.junit.ModulithExecutionCondition]
Caused by: java.util.ConcurrentModificationException
at org.springframework.modulith.junit.TestExecutionCondition.lambda$evaluate$1(TestExecutionCondition.java:90)
at org.springframework.modulith.junit.TestExecutionCondition.evaluate(TestExecutionCondition.java:84)
at org.springframework.modulith.junit.ModulithExecutionCondition.lambda$evaluateExecutionCondition$0(ModulithExecutionCondition.java:70)
at org.springframework.modulith.junit.ModulithExecutionCondition.evaluateExecutionCondition(ModulithExecutionCondition.java:63)
74+ of our 602 test classes failed this way in a single run, and it hits a different
set of classes on every run. Each failure reports Time elapsed: 0.001 s, i.e. the
condition evaluation itself fails, not the test.
Note this only surfaces when a reference commit is configured and the extension
actually computes module dependencies. With the default detector (no changes
detected, full suite executed) the code path is not reached, so the problem stays
hidden until change-aware selection is genuinely active.
Suspected cause
TestExecutionCondition caches module dependencies in an unsynchronized map:
private final Map<ApplicationModule, ApplicationModuleDependencies> dependencies = new HashMap<>();
...
var dependencies = this.dependencies.computeIfAbsent(it, m -> m.getAllDependencies(modules));
With junit.jupiter.execution.parallel.mode.classes.default=concurrent, multiple
threads evaluate the condition concurrently against the same instance. This looks
like the same class of problem as gh-1295 (MODULITH_TYPES, fixed for 2.0 M1),
but on a different field, so the earlier fix does not cover it.
The field is still a plain HashMap on main and in 2.1.0.
Suggested fix
Use ConcurrentHashMap for the dependencies field (no public API change).
Environment
- spring-modulith 2.0.6 (also reproducible per source inspection on 2.1.0 / main)
- Spring Boot 4.0.6, Java 25, Maven Surefire with
forkCount=1, reuseForks=true
- 3396 tests in 602 classes, 13 application modules
junit-platform.properties:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=same_thread
junit.jupiter.execution.parallel.mode.classes.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=6
spring.modulith.test.reference-commit set to a git SHA via environment variable
Workaround
Removing the spring-modulith-junit dependency (or setting
spring.modulith.test.skip-optimizations=true) avoids it, at the cost of losing
change-aware test selection.
Problem
With
spring-modulith-juniton the classpath and JUnit 5 parallel class executionenabled, test classes fail before executing with:
74+ of our 602 test classes failed this way in a single run, and it hits a different
set of classes on every run. Each failure reports
Time elapsed: 0.001 s, i.e. thecondition evaluation itself fails, not the test.
Note this only surfaces when a reference commit is configured and the extension
actually computes module dependencies. With the default detector (no changes
detected, full suite executed) the code path is not reached, so the problem stays
hidden until change-aware selection is genuinely active.
Suspected cause
TestExecutionConditioncaches module dependencies in an unsynchronized map:With
junit.jupiter.execution.parallel.mode.classes.default=concurrent, multiplethreads evaluate the condition concurrently against the same instance. This looks
like the same class of problem as gh-1295 (
MODULITH_TYPES, fixed for 2.0 M1),but on a different field, so the earlier fix does not cover it.
The field is still a plain
HashMaponmainand in 2.1.0.Suggested fix
Use
ConcurrentHashMapfor thedependenciesfield (no public API change).Environment
forkCount=1,reuseForks=truejunit-platform.properties:spring.modulith.test.reference-commitset to a git SHA via environment variableWorkaround
Removing the
spring-modulith-junitdependency (or settingspring.modulith.test.skip-optimizations=true) avoids it, at the cost of losingchange-aware test selection.