Checkpoint Lab — Maven Properties, Profiles, settings.xml, Mirrors, Proxies, Servers, and Environment-Specific Builds
Build the same project under two synthetic environments, prove profile/settings differences without changing dependency identity, reproduce and repair a mirror/server-ID failure, and verify that credentials never enter repository files or captured logs.
Learning objectives
- Run one Maven project under two explicit synthetic environments while keeping project/dependency coordinates stable.
- Capture active profiles, effective settings/POM, selected properties, dependency tree, artifact checksums, and isolated-repository state as evidence.
- Predict which model values should change and which artifact/dependency identities should remain stable before running the build.
- Reproduce a mirror/server-ID mismatch from an empty repository, preserve the 401-style failure, and repair one setting only.
- Prove the disposable credential value is absent from repository files and captured logs, then clean up only lab state.
~/.m2, global
settings, shared CI settings, or real credentials are modified.
./mvnw, grep, sha256sum, and
shell environment variables. On Windows use mvnw.cmd,
Select-String, Get-FileHash, and
$env:NAME. Maven profile/settings semantics are
cross-platform; file paths and shell quoting are not.
1. Checkpoint scenario and acceptance contract
You are preparing the same JVM project for a developer build and a
CI build. The Maven project coordinates remain
dev.academy:environment-boundary-lab:1.0.0. Both
environments resolve the same immutable dependency
dev.academy.fixture:academy-fixture:1.0.0 through the
same localhost mirror. Only explicit profile/settings properties
differ, and those values are intentionally not consumed by
artifact generation.
| Invariant / allowed difference | Expected |
|---|---|
| Project coordinates | Same in dev and CI. |
| Dependency coordinate/version | Same academy-fixture:1.0.0. |
| Maven/JDK/plugin pins | Same. |
| Mirror endpoint + ID | Same when configuration is healthy. |
| POM profile |
dev versus ci — explicit and
recorded.
|
| Settings profile |
settings-dev versus settings-ci.
|
academy.channel |
Different effective value by settings profile; not an artifact input. |
| JAR checksum | Expected equal if no environment property leaks into outputs. Investigate if different. |
2. Setup and preflight
Reuse the Lesson 2 project/fixture/server or recreate it in a fresh disposable directory. The repository fixture and Basic-auth server are part of the lab, not a hosted service.
java -version
javac -version
./mvnw -v
test -f pom.xml
test -f AuthRepoServer.java
test -f lab-repository/dev/academy/fixture/academy-fixture/1.0.0/academy-fixture-1.0.0.jar
Start the server in a separate terminal after setting disposable credentials interactively. Do not redirect the credential-setting command into evidence.
export ACADEMY_REPO_USER=academy-lab
read -r -s -p "Disposable lab password: " ACADEMY_REPO_PASSWORD; echo
export ACADEMY_REPO_PASSWORD
java --add-modules jdk.httpserver AuthRepoServer lab-repository
3. Two environment settings files — same routing, different non-secret profile property
Create these lab-only alternate settings files. They intentionally contain no literal credential value.
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<mirrors>
<mirror><id>academy-mirror</id><url>http://127.0.0.1:18080/</url><mirrorOf>academy-remote</mirrorOf></mirror>
</mirrors>
<servers>
<server><id>academy-mirror</id><username>${env.ACADEMY_REPO_USER}</username><password>${env.ACADEMY_REPO_PASSWORD}</password></server>
</servers>
<profiles>
<profile><id>settings-dev</id><properties><academy.channel>settings-dev</academy.channel></properties></profile>
</profiles>
<activeProfiles><activeProfile>settings-dev</activeProfile></activeProfiles>
</settings>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<mirrors>
<mirror><id>academy-mirror</id><url>http://127.0.0.1:18080/</url><mirrorOf>academy-remote</mirrorOf></mirror>
</mirrors>
<servers>
<server><id>academy-mirror</id><username>${env.ACADEMY_REPO_USER}</username><password>${env.ACADEMY_REPO_PASSWORD}</password></server>
</servers>
<profiles>
<profile><id>settings-ci</id><properties><academy.channel>settings-ci</academy.channel></properties></profile>
</profiles>
<activeProfiles><activeProfile>settings-ci</activeProfile></activeProfiles>
</settings>
Both files route repository ID academy-remote to mirror
ID academy-mirror and look up credentials under that
mirror ID. Their only intended effective-model difference is the
settings profile/property.
4. Prediction 1 — model changes, dependency identity does not
Prediction 1A: under
-Pdev -s lab/settings-dev.xml, both POM profile
dev and settings profile settings-dev are
active; academy.channel evaluates to
settings-dev.
Prediction 1B: under
-Pci -s lab/settings-ci.xml, active IDs and
academy.channel change, but the project coordinate and
dependency tree still contain exactly
academy-fixture:1.0.0.
Prediction 1C: because environment properties are not artifact inputs and output timestamps/plugins are pinned, the two JAR checksums should match. Treat a mismatch as evidence to investigate, not as something to normalize away.
5. Run synthetic environment A — developer
Use an empty local repository dedicated to this environment. Write
evidence outside target/.
mkdir -p checkpoint-evidence
export REPO_DEV="$PWD/.lab-m2-dev/repository"
HELP=org.apache.maven.plugins:maven-help-plugin:3.5.2
DEP=org.apache.maven.plugins:maven-dependency-plugin:3.11.0
./mvnw -s lab/settings-dev.xml -Pdev "$HELP:active-profiles" -Doutput=checkpoint-evidence/dev-active-profiles.txt
./mvnw -s lab/settings-dev.xml -Pdev "$HELP:effective-settings" -Doutput=checkpoint-evidence/dev-effective-settings.xml
./mvnw -s lab/settings-dev.xml -Pdev "$HELP:effective-pom" -Dverbose -Doutput=checkpoint-evidence/dev-effective-pom.xml
./mvnw -s lab/settings-dev.xml -Pdev "$HELP:evaluate" -Dexpression=academy.channel -q -DforceStdout > checkpoint-evidence/dev-channel.txt
set -o pipefail
./mvnw -s lab/settings-dev.xml -Pdev -Dmaven.repo.local="$REPO_DEV" clean package 2>&1 | tee checkpoint-evidence/dev-build.log
./mvnw -s lab/settings-dev.xml -Pdev -Dmaven.repo.local="$REPO_DEV" "$DEP:tree" -DoutputFile=checkpoint-evidence/dev-dependency-tree.txt
sha256sum target/environment-boundary-lab-1.0.0.jar > checkpoint-evidence/dev-jar.sha256
Confirm the active profile sources and the mirror/server IDs without
displaying the password. The fixture dependency must be present
under $REPO_DEV.
6. Run synthetic environment B — CI
Use a different empty repository. This prevents the developer run from proving the CI network path by accident.
export REPO_CI="$PWD/.lab-m2-ci/repository"
./mvnw -s lab/settings-ci.xml -Pci "$HELP:active-profiles" -Doutput=checkpoint-evidence/ci-active-profiles.txt
./mvnw -s lab/settings-ci.xml -Pci "$HELP:effective-settings" -Doutput=checkpoint-evidence/ci-effective-settings.xml
./mvnw -s lab/settings-ci.xml -Pci "$HELP:effective-pom" -Dverbose -Doutput=checkpoint-evidence/ci-effective-pom.xml
./mvnw -s lab/settings-ci.xml -Pci "$HELP:evaluate" -Dexpression=academy.channel -q -DforceStdout > checkpoint-evidence/ci-channel.txt
set -o pipefail
./mvnw -s lab/settings-ci.xml -Pci -Dmaven.repo.local="$REPO_CI" clean package 2>&1 | tee checkpoint-evidence/ci-build.log
./mvnw -s lab/settings-ci.xml -Pci -Dmaven.repo.local="$REPO_CI" "$DEP:tree" -DoutputFile=checkpoint-evidence/ci-dependency-tree.txt
sha256sum target/environment-boundary-lab-1.0.0.jar > checkpoint-evidence/ci-jar.sha256
7. Verify Prediction 1 independently
Compare the facts, not the entire files blindly. Effective POM/settings files will differ because profile IDs/properties differ. The dependency identity and intended artifact bytes should not.
cat checkpoint-evidence/dev-channel.txt checkpoint-evidence/ci-channel.txt
grep 'dev.academy.fixture:academy-fixture' checkpoint-evidence/dev-dependency-tree.txt
grep 'dev.academy.fixture:academy-fixture' checkpoint-evidence/ci-dependency-tree.txt
cat checkpoint-evidence/dev-jar.sha256 checkpoint-evidence/ci-jar.sha256
If the JAR hashes differ, inspect JAR contents/timestamps and effective plugin configuration before proceeding. Do not simply replace one artifact with the other and call them equivalent.
8. Prediction 2 — break only the server ID
Prediction 2A: with the same mirror URL,
credentials, POM, and dependency version, changing the server ID
from academy-mirror to academy-remote will
prevent Maven from applying those credentials to the selected
mirror.
Prediction 2B: a fresh local repository will force transport and reveal an authentication/401-style failure. A warm repository might hide it.
9. Introduce the controlled mismatch and preserve failure evidence
Create lab/settings-broken.xml from the Lesson 4 broken
example. Use a third empty repository and do not suppress Maven's
exit code.
export REPO_BROKEN="$PWD/.lab-m2-broken/repository"
mkdir -p "$REPO_BROKEN"
set -o pipefail
./mvnw -s lab/settings-broken.xml -Pci -Dmaven.repo.local="$REPO_BROKEN" clean package 2>&1 | tee checkpoint-evidence/broken-auth.log
Expected: Maven reaches http://127.0.0.1:18080/ but
cannot authenticate because the selected mirror has no matching
server credentials. Preserve the log before repair.
10. Repair one field and prove recovery
Restore the server ID to academy-mirror. Do not change
dependency coordinates, mirror URL, password value, or repository
content. Use another fresh repository so success cannot come from
the failed run's partial state.
export REPO_REPAIR="$PWD/.lab-m2-repair/repository"
mkdir -p "$REPO_REPAIR"
set -o pipefail
./mvnw -s lab/settings-ci.xml -Pci -Dmaven.repo.local="$REPO_REPAIR" clean package 2>&1 | tee checkpoint-evidence/repaired-auth.log
find "$REPO_REPAIR/dev/academy/fixture/academy-fixture/1.0.0" -maxdepth 1 -type f -print
The causal statement is now precise: server ID mismatch caused credentials not to apply to the selected mirror. Everything else stayed fixed.
11. Credential and evidence audit
Effective settings should keep the password redacted. Prove the actual disposable value is absent from project files and captured logs without printing it.
if grep -R -nF --exclude-dir=.git --exclude-dir='.lab-m2*' "$ACADEMY_REPO_PASSWORD" pom.xml lab checkpoint-evidence; then
echo "FAIL: disposable credential value appeared in a file"
exit 1
else
echo "PASS: disposable credential value absent from repository files and evidence"
fi
grep -R -nE '(BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16})' pom.xml lab checkpoint-evidence && exit 1 || true
12. Evidence manifest
Record the build-control facts that a release or incident reviewer needs without embedding secrets:
| Evidence | Why it matters |
|---|---|
| Maven/JDK versions | Build-runtime identity. |
Explicit -Pdev/-Pci |
Model branch chosen intentionally. |
| Active profile outputs | Proves settings + POM profile sources. |
| Effective settings | Proves mirror/server/proxy policy with passwords hidden. |
| Effective POM | Proves property/plugin/dependency model. |
| Dependency tree | Proves fixed dependency identity. |
| Isolated repo directories | Proves remote access was tested rather than inherited from warm cache. |
| JAR checksums | Detects unintended environment-specific artifact bytes. |
| Broken/repaired logs | Preserves root-cause and recovery evidence. |
| Secret scan result | Confirms lab credential value was not captured. |
13. Final verification checklist
| Check | Pass condition |
|---|---|
| Wrapper/runtime | Maven 3.9.16 and intended JDK captured. |
| Profiles | Dev/CI POM profiles explicit; settings profiles visible. |
| Properties |
Expected academy.channel values independently
evaluated.
|
| Coordinates | Project and fixture dependency identities unchanged. |
| Routing |
Both healthy environments use mirror ID
academy-mirror.
|
| Authentication failure | Broken server ID fails from fresh repository. |
| Repair | Only server ID mapping repaired; build succeeds from another fresh repository. |
| Artifact | Dev/CI JAR hashes match, or any difference is explained before acceptance. |
| Secrets | Actual disposable password absent from POM/settings/evidence files. |
| Normal state | No normal user/global settings or normal local repository modified/deleted. |
14. Cleanup / rollback
Stop AuthRepoServer. If retaining evidence, copy
checkpoint-evidence/ to a safe non-secret learning
location. Then remove only the disposable lab directory, including
.lab-m2-dev, .lab-m2-ci,
.lab-m2-broken, and .lab-m2-repair. Unset
the lab variables in the shell.
unset ACADEMY_REPO_USER ACADEMY_REPO_PASSWORD REPO_DEV REPO_CI REPO_BROKEN REPO_REPAIR
Knowledge check
Dev and CI effective POMs differ in an unused property, but JAR hashes match. What does that prove?
That the selected environment property changed effective model state but did not become an artifact input under this controlled build.
Why are separate dev/CI/broken/repair local repositories used?
To prevent one run's cached bytes from masking the network/authentication behavior being tested in another run.
The broken run reaches localhost and returns 401. Which single model relationship is intentionally wrong?
The selected mirror ID is academy-mirror, but
credentials are stored under a different server ID, so Maven
does not apply them to that mirror.
If the actual password appears in a captured log, is deleting the log enough?
No. Treat the credential as exposed and rotate/revoke it in the owning system, then fix the logging path.
Why is a matching JAR checksum not sufficient by itself for production acceptance?
You also need build/runtime identity, dependency/plugin identity, tests, model/repository policy, provenance/evidence, and the guarantee that the same artifact—not a rebuild—is promoted.
What production capability does this chapter add before multi-module Maven?
A controlled environment boundary: explicit profile/property inputs, inspectable settings, governed repository routing, credential indirection, and evidence-first diagnosis across developer/CI contexts.
15. Production build-engineering model and Chapter 09 bridge
Chapter 08 adds a critical production layer: the build is not only a POM plus dependencies/plugins. It runs inside an environment policy that selects profiles, properties, mirrors, proxies, server identities, and local state. Those controls are now explicit, inspectable, and separable from project source. Chapter 09 uses this stable environment boundary to scale Maven into multi-module projects, where parent inheritance, aggregation, and reactor ordering create a second dimension of model composition.
Official references and version notes
Version-sensitive statements in this lesson were checked against Apache Maven primary documentation on 2026-08-23. The mandatory path uses Maven 3.9.16 through Maven Wrapper 3.3.4, JDK 21 to run Maven, Java 17 as the project release target, Help Plugin 3.5.2, Dependency Plugin 3.11.0, Compiler Plugin 3.15.0, Resources Plugin 3.5.0, Surefire 3.5.6, and JAR Plugin 3.5.1. Maven 4-only profile syntax and preview behavior are not required here.
The checkpoint intentionally keeps environment profile properties out of artifact inputs so learners can distinguish “effective model differs” from “artifact must differ.”
- Maven — Settings Reference
- Maven — Introduction to Build Profiles
- Maven — POM Reference / Properties
- Maven — Using Mirrors for Repositories
- Maven — Setting up Multiple Repositories
- Maven — Security and Deployment Settings
- Maven — Injecting POM Properties via settings.xml
- Maven Help Plugin 3.5.2
- Maven Dependency Plugin 3.11.0
- Maven Compiler Plugin 3.15.0
- Maven JAR Plugin 3.5.1
- Maven Resources Plugin 3.5.0
- Maven Surefire 3.5.6
- Apache Maven Wrapper
- Maven 3.9.16 Release Notes
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.