Chapter 04Lesson 02~130 minutes

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.

Wrapper 3.3.4Maven 3.9.16ChecksumEffective SettingsIsolated Repository

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.
Version baseline — verified 2026-08-23. The required path uses JDK 21 and Apache Maven 3.9.16. Maven 3.9.16 is the current recommended GA and requires JDK 8+ to execute; Maven 3.10.0-rc-1 and Maven 4.0.0-rc-6 are previews and are not required. Maven Wrapper 3.3.4 is the current stable wrapper. Wrapper examples use the 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
Do not run this inside a valuable existing project. Wrapper generation writes project files. The lab directory should contain only disposable content.

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.

Windows path: PowerShell users can download the same two Apache files with 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?

Why verify Apache SHA-512 before deriving the wrapper SHA-256 pin?

What is the difference between MAVEN_USER_HOME and -Dmaven.repo.local in this lab?

Why should a second build normally download less than the first?

Why does a successful wrapper-only build not prove production reproducibility?

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.

Next lesson

Choose the right ownership boundary

Lesson 3 compares system Maven versus wrapper, shared versus isolated repositories, user settings versus project-safe configuration, and manual bootstrap versus archetypes as production design choices.

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.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.