ZetCode

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.

TypeDescription
only-scriptthe default; the scripts download Maven with wget, curl, or PowerShell; no JAR file
scriptthe scripts download maven-wrapper.jar first, which then downloads Maven
binlike script, but maven-wrapper.jar is stored in .mvn/wrapper and committed
sourcelike 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.

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

Maven Wrapper

In this article we have used the Maven Wrapper to pin, upgrade, and verify the Maven version of a project.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.