Hi,
I strongly think, we should keep in somewhere in the project folder (so
no m2 etc.) to keep it visible and show that it's temp. Similar to
Slawek: Having a "/target" will keep it out of git, with common
gitignores, so what about something like "project-local-repo/target" -
so it's clear on high level what the folder is (and not hided in
optional configuration .mvn folder) and due /target it's not added to git.
Matthias
Am 03.08.2026 um 14:09 schrieb Guillaume Nodet:
Hi all,
I'd like to get your input on the fix for GH-12646 — a race condition with
project-local-repo during parallel clean install builds.
The problem
When the root pom.xml has a parent that is also part of the reactor (e.g. a
super-pom), MultiThreadedBuilder schedules the parent first, then runs the
root project and child modules concurrently. Since clean and install are
placed into the same TaskSegment, there is no barrier between them — the
root project's maven-clean-plugin can delete target/ while sibling modules
are concurrently writing artifacts into target/project-local-repo, causing
the build to fail.
Approaches considered
1. Lock-based synchronization (ReentrantReadWriteLock in ReactorReader)
Acquire a write lock when the project owning target/ enters its clean
phase, and a read lock when installing artifacts. This prevents the crash
(concurrent access) but does not guarantee ordering — a module could
install artifacts before the root's clean starts, only to have them wiped.
It also adds complexity to ReactorReader for what is fundamentally an
architectural problem.
2. Move project-local-repo to ~/.m2/ (user home)
E.g. ~/.m2/local-repository/${projectName}_${hash}, similar to how IntelliJ
stores project caches. This avoids both the race and the git concern, but:
- Hard links (already used by ReactorReader) cannot cross filesystem
boundaries — if ~/.m2/ is on a different volume, every artifact becomes a
full copy
- Stale directories accumulate when projects are deleted/moved, with no
cleanup mechanism
- CI containers often share ~/.m2/ across builds, causing unwanted
accumulation
- Loses project locality (harder to inspect/debug)
3. Move project-local-repo to .mvn/project-local-repo (chosen)
Move the directory from target/project-local-repo to
.mvn/project-local-repo. Since maven-clean-plugin only deletes target/, the
race becomes structurally impossible. ReactorReader fully owns the
lifecycle — per-GAV cleanup when a project enters its clean phase, install
on project success. The existing hard-link optimization continues to work
since both directories are on the same filesystem.
The trade-off is that .mvn/project-local-repo needs to be gitignored. This
follows the Gradle convention where .gradle/ at the project root is
universally in .gitignore templates. Maven could also document this
convention or consider auto-appending the entry.
The fix itself is minimal — the core change is a single line in
getProjectLocalRepo(), and the rest is removing the lock infrastructure
(net -40 lines).
4. Keep in target/ but use maven-clean-plugin fast mode
The fast clean option (maven.clean.fast=true, since plugin 3.2) atomically
renames target/ instead of recursively deleting it. This would likely avoid
the race, but it's opt-in and not the default — users hitting the bug would
need to know about it.
FWIW, a PR has been raised on m-clean-p master branch (4.x) to refactor the
fast cleaner (fixing problems) and make it the default.
Open questions
- Is .mvn/ the right location, or should we consider a different
project-root directory?
- Should Maven auto-add .mvn/project-local-repo to .gitignore, or document
it as a convention?
- Are there other considerations I'm missing?
PR: https://github.com/apache/maven/pull/12650
Issue: https://github.com/apache/maven/issues/12646
Looking forward to your thoughts.
Cheers
Guillaume Nodet
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]