Apache Maven Installation, Wrapper, Settings, Local Repository, and Project Bootstrap: Guided Hands-On Workflow and Core Operations
Bootstrap a disposable Maven project, install and verify the project wrapper, isolate Maven state, inspect effective settings, and complete a wrapper-only build.
Learning objectives
- Bootstrap a minimal Maven project and explain the one-time role of system Maven in wrapper creation.
- Generate Maven Wrapper 3.3.4 for Maven 3.9.16 and convert the official release verification result into a wrapper SHA-256 pin.
- Run all normal project commands through the wrapper after bootstrap and verify Maven/JDK identity.
- Use an alternate settings file and isolated local repository without exposing credentials or mutating normal user cache state.
- Inspect downloaded coordinates, generated output, and effective settings as evidence of the build path.
only-script distribution type and pin the Maven
distribution with distributionSha256Sum after the
downloaded archive has first been verified against Apache's published
release checksum/signature. Re-check these versions before applying
the examples to production.
1. Preflight — use a disposable directory and inspect before creating
This lab intentionally uses system Maven only to create the wrapper.
After the wrapper exists, every project build uses
./mvnw or mvnw.cmd. If you do not have
system Maven installed, use an already-reviewed project/template
containing wrapper files rather than downloading random wrapper
scripts.
java -version
mvn -v
mkdir -p maven-bootstrap-lab/src/main/java/com/example
cd maven-bootstrap-lab
pwd
2. Create the smallest useful project by hand
Manual bootstrap keeps the learning surface small. The POM supplies project coordinates and a Java release target, while the source tree follows the convention introduced in Chapter 02. Maven lifecycle and plugin mechanics are intentionally deferred to Chapters 05–07.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>maven-bootstrap-lab</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
package com.example;
public final class App {
public static void main(String[] args) {
System.out.println("maven bootstrap lab");
}
}
At this point the repository has a project model but no
project-owned Maven launcher. Running mvn would still
depend on machine installation state.
3. Generate the wrapper once, then stop depending on system Maven
Use the fully qualified Wrapper Plugin coordinate so the bootstrap
step itself does not depend on plugin-prefix resolution. The current
plugin can install the only-script wrapper and select
Maven 3.9.16.
mvn org.apache.maven.plugins:maven-wrapper-plugin:3.3.4:wrapper \
-Dmaven=3.9.16 \
-Dtype=only-script
Expected project state now includes mvnw,
mvnw.cmd, and
.mvn/wrapper/maven-wrapper.properties. Review those
files before committing them. After this point, ordinary project
instructions should say ./mvnw / mvnw.cmd,
not “install the same Maven manually.”
ls -la mvnw mvnw.cmd .mvn/wrapper/maven-wrapper.properties
sed -n '1,120p' .mvn/wrapper/maven-wrapper.properties
4. Pin the distribution bytes, not only the version string
A versioned distributionUrl identifies where to fetch
Maven, but integrity verification answers a second question: are
these the expected bytes? The wrapper supports
distributionSha256Sum. The following Bash workflow
first checks the downloaded Maven archive against Apache's
separately published SHA-512 release checksum, then computes a
SHA-256 of those verified bytes and writes that value into the
wrapper properties.
mkdir -p .lab/verify
curl -fL -o .lab/verify/apache-maven-3.9.16-bin.zip \
https://dlcdn.apache.org/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.zip
curl -fL -o .lab/verify/apache-maven-3.9.16-bin.zip.sha512 \
https://downloads.apache.org/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.zip.sha512
EXPECTED_SHA512="$(awk '{print $1}' .lab/verify/apache-maven-3.9.16-bin.zip.sha512)"
ACTUAL_SHA512="$(sha512sum .lab/verify/apache-maven-3.9.16-bin.zip | awk '{print $1}')"
test "$EXPECTED_SHA512" = "$ACTUAL_SHA512"
DIST_SHA256="$(sha256sum .lab/verify/apache-maven-3.9.16-bin.zip | awk '{print $1}')"
printf 'Verified distribution SHA-256: %s\n' "$DIST_SHA256"
python - "$DIST_SHA256" <<'PY2'
from pathlib import Path
import sys
p=Path('.mvn/wrapper/maven-wrapper.properties')
lines=[x for x in p.read_text().splitlines() if not x.startswith('distributionSha256Sum=')]
lines.append('distributionSha256Sum='+sys.argv[1])
p.write_text('\n'.join(lines)+'\n')
PY2
The important reasoning is the chain: official release location → published release checksum/signature → independently computed match → SHA-256 pin committed with the project. Do not invent a checksum, disable verification to “make it work,” or obtain the expected hash from the same untrusted artifact you are trying to validate.
Invoke-WebRequest, compute
hashes with Get-FileHash -Algorithm SHA512 /
SHA256, compare the SHA-512 to Apache's published
value, then update distributionSha256Sum. The security
invariant is identical even though the shell syntax differs.
5. Isolate wrapper-home and local-repository state
The wrapper's downloaded Maven distribution and Maven's dependency/plugin local repository are different caches. Keep them separate in the lab so you can observe each one without touching normal user state.
mkdir -p .lab/wrapper-home .lab/m2repo
export MAVEN_USER_HOME="$PWD/.lab/wrapper-home"
./mvnw -Dmaven.repo.local="$PWD/.lab/m2repo" -v
New-Item -ItemType Directory -Force .lab\wrapper-home,.lab\m2repo | Out-Null
$env:MAVEN_USER_HOME = (Join-Path $PWD '.lab\wrapper-home')
.\mvnw.cmd "-Dmaven.repo.local=$PWD\.lab\m2repo" -v
MAVEN_USER_HOME here isolates wrapper-managed user
state. The explicit maven.repo.local property selects
Maven's dependency/plugin local repository. Do not assume one
setting replaces the other.
6. Use an alternate settings file that contains no real credentials
For a disposable reproducibility lab, an explicit
-s path removes ambiguity about which user settings are
applied. The file below intentionally contains no repository
credentials or mirrors.
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<interactiveMode>false</interactiveMode>
<offline>false</offline>
</settings>
./mvnw -s .lab/settings.xml \
-Dmaven.repo.local="$PWD/.lab/m2repo" \
org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-settings \
-Doutput=.lab/effective-settings.xml
grep -n "localRepository\|offline\|mirror\|proxy" .lab/effective-settings.xml | head -40
The Help Plugin hides passwords by default. Never add
-DshowPasswords=true to shared diagnostic instructions.
7. Run the first wrapper-only build
The first clean build has two independent cold-start costs: the wrapper may need to obtain Maven 3.9.16, and Maven may need to resolve lifecycle plugins/dependencies into the isolated local repository. Capture that separately from compilation time.
./mvnw -s .lab/settings.xml \
-Dmaven.repo.local="$PWD/.lab/m2repo" \
-B -ntp package
Expected output includes a target/ directory and a
project JAR. The isolated local repository should now contain
Maven/plugin coordinates that were absent before. Those downloaded
files are inputs/cache state; target/ is project
output.
find target -maxdepth 2 -type f -print | sort
find .lab/m2repo -maxdepth 5 -type f | sed -n '1,60p'
jar tf target/maven-bootstrap-lab-1.0.0-SNAPSHOT.jar | sed -n '1,80p'
8. Repeat the build and explain what changed
Run the same command a second time without deleting the isolated repository. Dependency/plugin downloads should largely disappear because the local repository is warm. Maven still evaluates the project and executes its lifecycle; unlike Gradle, Maven does not use Gradle-style task up-to-date semantics.
./mvnw -s .lab/settings.xml \
-Dmaven.repo.local="$PWD/.lab/m2repo" \
-B -ntp package
Do not call the second run “reproducible” merely because it is faster. Reproducibility concerns equivalent outputs from equivalent declared inputs; cache warmth is a performance/state-reuse observation.
9. Optional contrast — template bootstrap
The official Quickstart Archetype 1.5 can generate a conventional Java project. That can reduce setup work, but it adds template content that you must review. For an organization template, pin the archetype version and treat it as build-supply-chain input.
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=archetype-lab \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.5 \
-DinteractiveMode=false
10. Challenge — explain a “wrong Maven” report
A teammate says “the repository pins Maven 3.9.16, but CI logged
Maven 3.9.14.” Without changing the POM, list the first three facts
you would inspect. A strong answer checks whether CI invoked
mvn instead of ./mvnw, reviews
.mvn/wrapper/maven-wrapper.properties, and checks
whether the CI log actually came from the expected workspace/commit.
Knowledge check
Why is system Maven still used once in this lab?
It bootstraps the wrapper files. After those files exist and are reviewed/committed, normal project execution should use the wrapper.
Why verify Apache SHA-512 before deriving the wrapper SHA-256 pin?
The SHA-512/signature provides independent release-integrity evidence. Computing a SHA-256 from already verified bytes then gives the exact algorithm/value the wrapper expects.
What is the difference between MAVEN_USER_HOME and
-Dmaven.repo.local in this lab?
The first isolates wrapper-managed user state/distribution storage; the second explicitly selects Maven’s dependency/plugin local repository.
Why should a second build normally download less than the first?
The isolated local repository is now warm and already contains many resolved plugins/dependencies, so Maven can reuse those cached inputs.
Why does a successful wrapper-only build not prove production reproducibility?
JDK/toolchains, plugin/dependency versions, settings/repository policy, environment inputs, and output normalization remain separate reproducibility controls.
Summary
You bootstrapped a Maven project, generated a project-owned wrapper, verified and pinned the Maven distribution, separated wrapper state from Maven local-repository state, used an explicit safe settings file, and proved the first wrapper-only build from observable files and logs. From here on, Chapter 04 treats the wrapper as the normal entry point.
Official references and version notes
This workflow intentionally keeps hosted repositories and credentials out of the mandatory lab. The checksum procedure and wrapper parameters were verified against Apache Maven Wrapper 3.3.4 documentation.
- Apache Maven — Installation
- Apache Maven — Download and current/preview release status
- Apache Maven Wrapper
- Maven Wrapper Plugin — wrapper:wrapper
- Maven Settings Reference
- Maven — Using Mirrors for Repositories
- Maven Local Repositories
- Maven Help Plugin — help:effective-settings
- Maven Quickstart Archetype
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.