IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.
Maven Cache Internals
Understanding which parts of the Maven local repository (~/.m2) are safe to cache across CI
runners and which parts cause stale-resolution or non-determinism is the foundation of this
action’s partition design for Maven.
Maven local repository structure
| Path | Purpose | Cache for CI? |
|---|---|---|
repository/ |
Cached artifact and metadata store | Yes, selectively |
wrapper/dists/ |
Maven Wrapper distribution downloads | Yes |
settings-security.xml |
Encrypted master password for settings.xml |
No |
Artifact store: repository/
Maven’s artifact store uses a fully content-deterministic layout:
repository/<groupId path>/<artifactId>/<version>/<artifactId>-<version>.<ext>
Because the path is derived solely from the artifact coordinates, the content of any given path is
identical regardless of which runner downloaded it. This makes repository/ fully portable across
machines, operating systems, and architectures.
What is portable
| Contents | Why portable |
|---|---|
| Artifact JARs, WARs, POMs, sources, javadoc | Content-addressed; path encodes coordinates, not host path |
| Plugin JARs and their dependencies | Same content-addressed layout as regular artifacts |
Release metadata (maven-metadata-local.xml) |
Describes locally installed artifacts; stable across runners |
What is not portable
| Contents | Reason |
|---|---|
*.lastUpdated files |
Record per-runner timestamp and remote-check outcome; cause stale cache misses |
resolver-status.properties |
Written by Maven Resolver (Aether) at group/artifact metadata level to track which remotes were contacted; same per-runner semantics as .lastUpdated — a frequent distributed-merge conflict source when parallel workers both resolve Maven plugins |
_remote.repositories files |
Map artifacts to source remote; invalid when remotes differ across environments |
*.lock files |
PID-bearing resolver lock files; cause hangs or errors on restore |
All four categories are unconditional hard excludes in every active partition.
*.lastUpdated in depth
When Maven contacts a remote repository to check for an artifact or metadata update, it writes a
.lastUpdated file recording the timestamp of the check and which remote it contacted. On the
next invocation, Maven reads this file and skips the remote check if sufficient time has not
elapsed (controlled by updatePolicy).
When this file is cached and restored on a different runner:
- The timestamp appears recent, so Maven skips the remote check.
- If a newer version has since been published, Maven silently uses the stale cached artifact.
- If the original check was a failure (e.g. artifact not yet published), Maven continues to report the artifact as missing even though it now exists remotely.
Excluding .lastUpdated files forces each runner to perform fresh remote checks, which is the
correct behaviour for a CI environment where artifact publication is ongoing.
resolver-status.properties in depth
Maven Resolver (Aether) writes a resolver-status.properties file in the local repository at the
group/artifact metadata level (e.g. org/apache/maven/plugins/resolver-status.properties). The
file records, for each configured remote repository, the timestamp of the last metadata check and
whether it succeeded or failed.
This file has the same semantics as .lastUpdated:
- It is per-runner state (different runners may have different remote repository configurations).
- Restoring it on another runner suppresses fresh remote checks, potentially hiding newly published artifacts or causing stale failure states to persist.
Additionally, in distributed multi-job builds it is a frequent merge conflict source: every
worker job that resolves any Maven plugin (which happens for nearly every Maven goal invocation)
writes its own version of this file for the org/apache/maven/plugins/ path. The files differ
across workers because each worker contacts Maven Central at a different moment and accumulates
status entries for only the plugins it used. Because the files differ in content, the aggregator
cannot merge them without silently dropping one worker’s view of the world.
The solution is to exclude them unconditionally so the delta never contains resolver-status.properties
entries and the conflict cannot arise.
_remote.repositories in depth
After resolving an artifact from a remote repository, Maven writes a _remote.repositories file
listing the remote repository ID that served the artifact. On subsequent invocations, if the local
repository contains the artifact but _remote.repositories does not match the current active
remotes, Maven re-downloads the artifact.
This file is not portable because:
- Different CI environments may have different
settings.xmlconfigurations, different repository IDs, or mirror rules that map the same URL to a different logical ID. - Restoring a
_remote.repositoriesfile from another runner may either suppress or trigger unnecessary re-downloads depending on whether the repository IDs match.
Wrapper distributions: wrapper/dists/
When projects use the Maven Wrapper, the Wrapper script downloads the Maven distribution into
~/.m2/wrapper/dists/. The distribution is identified by its URL, making the path
deterministic and portable. This directory is covered by the wrapper-dists partition.
Portability summary
Safe to cache:
| Pattern | Role |
|---|---|
repository/** |
Immutable content-addressed artifacts |
wrapper/dists/** |
Downloaded Maven distributions |
Always excluded:
| Pattern | Reason |
|---|---|
**/*.lastUpdated |
Stale remote-check markers; cause silent resolution misses |
**/_remote.repositories |
Per-remote provenance markers; not portable across environments |
**/*.lock |
PID-bearing resolver locks; cause hangs on restore |