Chapter 08Lesson 05~195 minutes

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.

Checkpoint LabTwo EnvironmentsMirror AuthenticationEvidenceReproducibility

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.
Current baseline — verified 2026-08-23. Labs use Maven 3.9.16 via Maven Wrapper 3.3.4, JDK 21 to run Maven, Java 17 as the compiler 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. Lab resolution uses project-relative isolated local repositories. No normal ~/.m2, global settings, shared CI settings, or real credentials are modified.
Cross-platform note: POSIX examples use ./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
Production incident rule: if a real credential was exposed, rotate/revoke it in the owning system. A clean Git diff after the fact does not make the old credential safe.

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?

Why are separate dev/CI/broken/repair local repositories used?

The broken run reaches localhost and returns 401. Which single model relationship is intentionally wrong?

If the actual password appears in a captured log, is deleting the log enough?

Why is a matching JAR checksum not sufficient by itself for production acceptance?

What production capability does this chapter add before multi-module Maven?

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.”

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.