Stop Asking Teammates to Install Maven: Commit the Wrapper Instead
Commit the Maven Wrapper (mvnw) to pin Maven versions per project, eliminate "works on my machine" builds, and make upgrades auditable. Includes generation steps, upgrade workflow, CI integration, and common pitfalls like ignoring the wrapper JAR or Windows line-ending issues.
03 Aug 2025, 04:21 UTC

The problem: "Works on my machine" Maven builds
Every Java team knows the drill. A new developer clones the repo, runs mvn clean verify, and the build fails — or worse, passes but produces different artifacts because their system Maven is 3.9.6 while the CI agent runs 3.8.8. Someone updates a plugin that requires a newer Maven version, half the team upgrades, the other half doesn't, and the next release candidate breaks in subtle ways.
The fix isn't documentation or a wiki page. It's a 60 KB JAR and two shell scripts committed to the repository.
What the wrapper actually does
The Maven Wrapper (mvnw / mvnw.cmd) is a tiny bootstrap that downloads a declared Maven distribution on first use, caches it under ~/.m2/wrapper/dists, and then delegates every command to that exact version. No system-wide Maven installation required. The version is pinned in .mvn/wrapper/maven-wrapper.properties via a distributionUrl — typically an Apache mirror URL containing the version number.
Because the wrapper files live in the repo, every developer and every CI job runs the same Maven version by default. Upgrading becomes a deliberate, reviewable commit rather than a silent environment drift.
Generating and committing the wrapper
Run the wrapper goal once from the project root (you need a system Maven installed for this initial step):
mvn -N io.takari:maven:wrapper
# or, if you prefer the official plugin
mvn wrapper:wrapper
This creates:
mvnw— POSIX shell scriptmvnw.cmd— Windows batch script.mvn/wrapper/maven-wrapper.jar— the bootstrap JAR (~60 KB).mvn/wrapper/maven-wrapper.properties— containsdistributionUrl
Commit all four files. A common mistake is adding maven-wrapper.jar to .gitignore because it's a JAR; doing so breaks the wrapper for everyone else. The JAR is version-specific and must be tracked.
Worked example: upgrading Maven from 3.9.6 to 3.9.9
- Open
.mvn/wrapper/maven-wrapper.propertiesand change thedistributionUrlline:distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip - Regenerate the wrapper JAR so it matches the new version:
./mvnw -N io.takari:maven:wrapper - Verify the upgrade on a clean clone:
git clone /tmp/fresh-clone cd fresh-clone ./mvnw -v # Should print: Apache Maven 3.9.9 ... - Commit the changed properties file and the updated
maven-wrapper.jar. The git diff now shows exactly what changed and when — auditable, reversible, no "who upgraded Maven?" Slack threads.
CI/CD integration: cache dependencies, not Maven
Modern CI systems (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines) should cache ~/.m2/repository but invoke ./mvnw directly. Example GitHub Actions step:
- name: Build with Maven Wrapper
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
restore-keys: maven-${{ runner.os }}-
- run: ./mvnw -B clean verify
This gives you fast dependency resolution (cached artifacts) plus zero Maven-installation maintenance on build agents. The first run on a fresh agent downloads ~8–12 MB for the Maven distribution; subsequent runs are instant.
Trade-offs and gotchas
| Concern | Reality |
|---|---|
| Repository size | +60 KB for the JAR, plus two tiny scripts. Negligible for almost any repo. |
| First-build latency | One-time ~8–12 MB download per machine/agent. Acceptable for most teams; can be avoided by pre-warming CI caches or hosting the distribution internally. |
| Corporate firewalls / air-gapped envs | Default Apache URL may be blocked. Point distributionUrl to an internal Nexus/Artifactory mirror and keep that URL in sync with upgrades. |
| Line endings on Windows | If core.autocrlf=true, mvnw gets CRLF and breaks. Add to .gitattributes:
|
| JDK version not pinned | The wrapper only pins Maven. Pair it with .tool-versions (asdf), SDKMAN, or CI-defined JDK for full reproducibility. |
| Old wrapper JARs | Pre-3.1.0 wrappers lack modern flags like --no-transfer-progress. Regenerate after major Maven upgrades. |
Verify it works — today
Run these three checks on a clean clone (or ask a teammate to):
./mvnw -vprints the exact version frommaven-wrapper.properties.rm -rf ~/.m2/wrapper/dists && ./mvnw help:effective-pomre-downloads and succeeds.git ls-files .mvn/wrapper/maven-wrapper.jarreturns the file — if empty, the JAR isn't tracked.
If all three pass, your builds are now reproducible by default. No more "install Maven first" onboarding steps, no more version drift between laptop and CI. The wrapper is a small commit that pays off every time someone clones the repo.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.