Maven Wrapper
last modified November 8, 2026
In this article we show how to use the Maven Wrapper to pin the Maven version of a project. The examples use Maven 3.10, the Wrapper plugin 3.3.4, and Java 25.
The Maven Wrapper is a pair of small scripts, mvnw
for Unix-like systems and mvnw.cmd for Windows, committed
together with the project. When we run ./mvnw instead of
mvn, the script downloads the Maven version written in the
project's configuration, caches it, and runs the build with it. Everyone who
builds the project, developers and CI servers alike, uses the same Maven
version, and nobody has to install Maven at all; only a JDK is needed.
The Maven version matters more than it may seem. Different Maven releases bring different default plugin versions and defaults such as the source encoding, so the same POM can build differently, as we show below.
Adding the wrapper
The wrapper goal of the Wrapper plugin adds the wrapper to a
project. It is run once, with an installed Maven, in the root directory of
the project.
By default, the goal configures the Maven version that runs it, here 3.10.0.
It creates the two scripts and the
.mvn/wrapper/maven-wrapper.properties file. The
distributionUrl property tells the scripts where to download
Maven from. The default distribution type, only-script, needs no
JAR file: the script downloads Maven with wget or
curl by itself.
$ mvn wrapper:wrapper
[INFO] --- wrapper:3.3.4:wrapper (default-cli) @ app ---
[INFO] Unpacked only-script type wrapper distribution org.apache.maven.wrapper:maven-wrapper-distribution:zip:only-script:3.3.4
[INFO] Configuring .mvn/wrapper/maven-wrapper.properties to use Maven 3.10.0 and download from https://repo.maven.apache.org/maven2
[INFO] BUILD SUCCESS
...
$ tree -a
.
├── .mvn
│ └── wrapper
│ └── maven-wrapper.properties
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── main
└── java
└── com
└── example
└── App.java
7 directories, 5 files
$ cat .mvn/wrapper/maven-wrapper.properties
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip
Running the wrapper
The mvnw script accepts the same arguments as mvn.
On the first run, it downloads the Maven distribution and unpacks it into
~/.m2/wrapper/dists. The download is silent; we notice it only
because the first run takes a few seconds longer.
The Maven home line shows that the build runs with the Maven
installed by the wrapper, not with the one on the PATH. The
directory name is a hash of the distribution URL, so different URLs for the
same version do not clash. Later runs find the unpacked distribution and start
immediately. Projects that use the same Maven version share it.
$ ./mvnw -v Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472) Maven home: /home/user/.m2/wrapper/dists/apache-maven-3.10.0/dae9a06d Java version: 25.0.3, vendor: Amazon.com Inc., runtime: /home/user/.sdkman/candidates/java/25.0.3-amzn Default locale: en, platform encoding: UTF-8, time zone: Europe/Bratislava OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix" $ ls ~/.m2/wrapper/dists/apache-maven-3.10.0 dae9a06d $ ./mvnw package [INFO] --- resources:3.5.0:resources (default-resources) @ app --- [INFO] --- compiler:3.16.0:compile (default-compile) @ app --- [INFO] --- resources:3.5.0:testResources (default-testResources) @ app --- [INFO] --- compiler:3.16.0:testCompile (default-testCompile) @ app --- [INFO] --- surefire:3.6.0:test (default-test) @ app --- [INFO] --- jar:3.5.1:jar (default-jar) @ app --- [INFO] BUILD SUCCESS ... $ java -cp target/app-1.0.0.jar com.example.App Built with the Maven Wrapper
Verbose mode
The MVNW_VERBOSE environment variable set to true
makes the script report what it does. The value debug also
traces every line of the shell script. We show only the main lines of the
output; the download and unzip progress is left out.
The script did not find the distribution, so it downloaded it with
wget and unpacked it. Verbose mode is the first thing to try when
the wrapper fails on a CI server, for instance because of a proxy or a
missing unzip command.
$ MVNW_VERBOSE=true ./mvnw -v Couldn't find MAVEN_HOME, downloading and installing it ... Downloading from: https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip Found wget ... using wget Found extracted Maven distribution directory: apache-maven-3.10.0 Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472) Maven home: /home/user/.m2/wrapper/dists/apache-maven-3.10.0/dae9a06d ...
Pinning a Maven version
The -Dmaven option of the wrapper goal selects the
Maven version. We switch our project to Maven 3.9.11 and build it with the
wrapper.
The build uses the default plugin versions of Maven 3.9.11, such as compiler
3.13.0 instead of 3.16.0. Maven 3.9 does not default the source encoding to
UTF-8, so the resources and compiler plugins warn that the build depends on
the platform encoding. The WARNING lines at the top come from the
JVM: Maven 3.9.11 uses a library that calls sun.misc.Unsafe
methods, which JDK 25 reports.
An upgrade is the same goal with a new version. Running it through
./mvnw uses the Maven version pinned so far. The new version is
downloaded on the next run. We commit the changed properties file, and every
checkout of the project gets the upgrade.
$ mvn wrapper:wrapper -Dmaven=3.9.11 [INFO] Configuring .mvn/wrapper/maven-wrapper.properties to use Maven 3.9.11 and download from https://repo.maven.apache.org/maven2 [INFO] BUILD SUCCESS ... $ ./mvnw package WARNING: A terminally deprecated method in sun.misc.Unsafe has been called WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/home/user/.m2/wrapper/dists/apache-maven-3.9.11/a2d47e15/lib/guice-5.1.0-classes.jar) WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release [INFO] --- resources:3.3.1:resources (default-resources) @ app --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] --- compiler:3.13.0:compile (default-compile) @ app --- [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] --- resources:3.3.1:testResources (default-testResources) @ app --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ app --- [INFO] --- surefire:3.2.5:test (default-test) @ app --- [INFO] --- jar:3.4.1:jar (default-jar) @ app --- [INFO] BUILD SUCCESS ... $ ./mvnw wrapper:wrapper -Dmaven=3.10.0 [INFO] Configuring .mvn/wrapper/maven-wrapper.properties to use Maven 3.10.0 and download from https://repo.maven.apache.org/maven2 [INFO] BUILD SUCCESS ... $ ./mvnw -v Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472) ...
Verifying the distribution
The wrapper downloads and runs code from the network. To make sure that it
runs exactly the distribution we expect, we add its SHA-256 checksum to the
properties file. Maven Central publishes SHA-512 checksums of the Maven
distributions, but the wrapper needs SHA-256, so we compute it ourselves. The
distributionSha256Sum option writes it to the properties file.
The wrapper verifies the checksum after each download with
sha256sum or shasum. When we upgrade Maven, we must
update the checksum as well.
$ curl -s https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip | sha256sum 1f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff - $ mvn wrapper:wrapper \ -DdistributionSha256Sum=1f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff [INFO] Configuring .mvn/wrapper/maven-wrapper.properties to use Maven 3.10.0 and download from https://repo.maven.apache.org/maven2 [INFO] BUILD SUCCESS ... $ cat .mvn/wrapper/maven-wrapper.properties wrapperVersion=3.3.4 distributionType=only-script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip distributionSha256Sum=1f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff $ ./mvnw -v Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472) ...
Checksum mismatch
If the checksum does not match, the wrapper refuses to install the distribution and exits with status 1. Here we use a wrong checksum.
The checksum is verified only when the distribution is downloaded. A
distribution that is already unpacked in ~/.m2/wrapper/dists is
not checked again, so a wrong checksum may go unnoticed on a developer
machine and fail only on a fresh CI runner.
$ grep Sha256 .mvn/wrapper/maven-wrapper.properties distributionSha256Sum=0f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff $ ./mvnw -v Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property. $ echo $? 1
Distribution types
The -Dtype option of the wrapper goal selects how
the wrapper downloads Maven.
| Type | Description |
|---|---|
only-script | the default; the scripts download Maven with wget, curl, or PowerShell; no JAR file |
script | the scripts download maven-wrapper.jar first, which then downloads Maven |
bin | like script, but maven-wrapper.jar is stored in .mvn/wrapper and committed |
source | like script, but also stores MavenWrapperDownloader.java, which is compiled and run to fetch the JAR when neither wget nor curl is available |
The only-script type is the simplest and keeps binary files out
of the repository. The bin and source types are
useful when the build machine has neither wget nor
curl. With the script
and bin types, the properties file also contains a
wrapperUrl property pointing to the JAR file.
Environment variables
The wrapper scripts read several environment variables.
MVNW_VERBOSE—truefor verbose output,debugfor a shell traceMVNW_REPOURL— the base URL of a repository mirror; it replaceshttps://repo.maven.apache.org/maven2in thedistributionUrl, which is useful behind a corporate repository managerMVNW_USERNAMEandMVNW_PASSWORD— credentials for a repository that requires authenticationMAVEN_USER_HOME— the directory with thewrapper/distscache;~/.m2by default
On a CI server, caching the ~/.m2/wrapper directory together with
~/.m2/repository avoids downloading Maven in every build.
Version control
We commit mvnw, mvnw.cmd, and the
.mvn/wrapper/maven-wrapper.properties file. With the
bin type, we also commit maven-wrapper.jar; with
the script and source types, we add the downloaded
JAR file to .gitignore. The mvnw script must keep
its executable bit; on Windows, where the file system does not store it, the
git update-index --chmod=+x mvnw command sets it in the
repository. The script also needs Unix line endings, so a
.gitattributes rule such as mvnw text eol=lf keeps
Git from converting them.
On Windows, the project is built with mvnw.cmd, or simply
mvnw in the Command Prompt, or .\mvnw in PowerShell.
The .mvn directory has one more role: Maven treats the directory
that contains it as the root of a multi-module project, and it reads the
.mvn/maven.config and .mvn/jvm.config files from
there.
Source
In this article we have used the Maven Wrapper to pin, upgrade, and verify the Maven version of a project.
Author
List all Java tutorials.