Chapter 13Lesson 04~175 minutes

Maven Performance, Parallel Builds, Daemon Options, Reproducible Builds, and Troubleshooting: Diagnostics, Failure Modes, Security, and Performance

Diagnose Maven performance and reproducibility failures involving hidden ordering, unsafe shared state, timestamp drift, stale repository state, and sensitive debug logs.

TroubleshootingCritical PathNon-determinismRepository StateDebug Logs

Performance failures are often correctness failures wearing a timing label. The safest investigation preserves the original evidence, removes one variable at a time, and treats caches, parallelism, persistent processes, and debug logs as state with explicit trust boundaries.

Current baseline — verified 2026-08-24. Mandatory labs use Apache Maven 3.9.16 through Maven Wrapper 3.3.4, JDK 21 as the build runtime, and Java 17 as the compiler release target. Maven Daemon 1.0.6 is an optional separate tool; Maven Daemon 2.0.0-rc-3 and Maven 4.0.0-rc-6 are preview releases and are not the production baseline. The intentionally broken examples operate only in disposable project directories and isolated local repositories. They never modify the normal user Maven repository, production credentials, or shared CI state.

Learning objectives

  • Apply the chapter diagnostic sequence before changing caches or thread counts.
  • Diagnose a serial-only success caused by an undeclared module relationship.
  • Recognize plugin/shared-state hazards in parallel builds.
  • Reproduce and repair byte drift caused by build-time data.
  • Use a fresh isolated local repository to test a stale-state hypothesis without destroying evidence.
  • Handle -X logs as potentially sensitive operational artifacts.

1. Diagnostic sequence: preserve evidence before mutation

Use the same sequence whether the symptom is slowness, a parallel-only failure, or checksum drift:

  1. Preserve the concise failing log, timing, checksum, reports, and workspace state.
  2. Confirm wrapper/Maven/JDK identity with ./mvnw -v.
  3. Inspect declared and effective POM configuration.
  4. Inspect dependency and reactor relationships.
  5. Compare repository/cache/filesystem state using a fresh isolated repository if needed.
  6. Inspect the specific compiler/test/plugin failure.
  7. Apply the least destructive correction.
  8. Rebuild under a controlled state and verify outputs—not just exit code.

2. Failure: serial build hides an undeclared module ordering dependency

Imagine modules producer and consumer are merely listed in that order under <modules>. A plugin in producer writes ../shared/generated.txt; consumer reads the same file. There is no Maven dependency/plugin edge between them. Serial execution may appear reliable because directory declaration order becomes a tie-breaker. Parallel execution can expose the missing relationship.

Serial symptom:
[INFO] Building producer ... SUCCESS
[INFO] Building consumer ... SUCCESS

Parallel symptom:
[INFO] Building producer ...
[INFO] Building consumer ...
[ERROR] Required file ../shared/generated.txt does not exist

The correct repair is not “use -T 1 forever.” Model the relationship. If consumer truly consumes producer's build artifact, declare a project dependency or redesign the generator so its output is attached/consumed through Maven's graph. If the file is only scratch state, keep it module-local under each module's build directory.

3. Failure: a plugin goal or configuration is not safe for overlap

Maven's goal documentation can state that a goal is thread-safe. A goal that is not marked thread-safe may cause Maven to warn during parallel execution. Even a thread-safe goal can be configured unsafely—for example, multiple modules writing the same absolute report file.

Evidence Meaning Correction
Maven warns goal is not thread-safe Plugin author has not declared concurrent support. Check for a newer compatible plugin; keep affected build serial until behavior is understood.
Goal is thread-safe, files collide Project configuration introduced shared mutable output. Use module-local ${project.build.directory} paths or a deliberate aggregator step.
Fixed port collision in tests External resource, not Maven reactor semantics. Allocate isolated ports/resources; do not hide with random retries.
Parallel-only flake with no obvious warning Could be application/plugin/shared-state race. Reduce concurrency to reproduce, preserve reports, then isolate the shared state.

4. Intentionally broken example: wall-clock metadata changes the JAR

This resource filtering example deliberately embeds Maven's current build timestamp into a packaged file. It teaches why archive timestamp normalization cannot repair arbitrary dynamic content.

<properties>
  <project.build.outputTimestamp>2026-08-24T00:00:00Z</project.build.outputTimestamp>
  <maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ssXXX</maven.build.timestamp.format>
</properties>
<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>
# src/main/resources/build-info.properties
built.at=${maven.build.timestamp}
./mvnw -Dmaven.repo.local="$PWD/.lab/repo-a" clean package
sha256sum target/*.jar | tee evidence/build-a.sha256
sleep 2
./mvnw -Dmaven.repo.local="$PWD/.lab/repo-a" clean package
sha256sum target/*.jar | tee evidence/build-b.sha256
unzip -p target/*.jar build-info.properties | tee evidence/build-info.txt

Expected diagnosis: the fixed project.build.outputTimestamp normalizes supported archive-entry timestamps, but the resource content itself changes because ${maven.build.timestamp} changes each Maven invocation. Repair by removing wall-clock data from the artifact or replacing it with a stable declared input, such as a release version/source commit supplied by the release process. Then rebuild twice and compare checksums again.

5. Failure: warm repository state creates a phantom success or failure

A warm local repository may contain a previously installed sibling artifact, cached metadata, or an earlier failed-transfer marker. Do not destroy the normal repository to test this hypothesis. Create a new lab repository and repeat the smallest failing resolution/build.

./mvnw -v | tee evidence/identity.txt
./mvnw -Dmaven.repo.local="$PWD/.lab/repo-existing" -pl :app verify   > evidence/existing-repo.log 2>&1 || true
./mvnw -Dmaven.repo.local="$PWD/.lab/repo-fresh" -pl :app -am verify   > evidence/fresh-repo.log 2>&1 || true

If the results differ, you have evidence that repository state matters. Next inspect the exact coordinate/origin/metadata involved. A fresh repository is a comparison control, not permission to erase the original state.

6. Failure: excessive debug logging creates a second incident

Teams sometimes enable -X globally during an outage and upload the full log. That can turn a build failure into an information-exposure problem. Preserve the normal error first, use -e for stack context, and enable -X only for the smallest reproducible invocation.

Security-sensitive: inspect debug output for repository URLs, proxy/server identifiers, local paths, environment-derived values, command-line properties, private coordinates, and any credential-like material before sharing it. Never pass real secrets on the command line to make debugging easier.

7. Diagnose the slow stage, not the whole command

A build that slows from 40 to 90 seconds after a repository outage probably needs resolver evidence; a build that spends 70 seconds in tests needs test evidence; a build whose packaging step alone grows needs archive/plugin evidence. Compare timestamps around meaningful phase/goal boundaries and Maven's own log sequence before adding profilers or commercial analytics.

Observed region Useful first evidence Misleading first reaction
Resolution/download Cold/warm isolated repo, transfer timings, repository origin. Increase reactor threads blindly.
Model/configuration Effective POM, profiles, plugin versions. Delete caches.
Compilation Source count, compiler/JDK identity, incremental assumptions. Disable tests.
Tests Surefire/Failsafe reports and durations. Treat retries as performance optimization.
Packaging Artifact size, generated resources, checksum comparison. Assume JAR creation is always negligible.
CI provisioning Runner queue/startup/resource limits. Change Maven flags when Maven has not started yet.

8. Least-destructive repair and controlled rebuild

A credible repair states which variable changed. Examples: add the missing reactor dependency; move shared output into module-local target state; pin/update a plugin after confirming thread-safety support; replace dynamic timestamp content with a stable input; or use a fresh isolated repository to prove stale-state involvement. Then rerun the same scope and compare reports/artifacts/checksums.

Knowledge check

A build succeeds serially but fails only with -T 2 because consumer reads a file producer happens to create first. What is the root defect?

If Maven warns a goal is not thread-safe, should CI simply ignore the warning after one successful run?

Why can project.build.outputTimestamp be set correctly while JAR checksums still differ?

What is the first safe way to test whether local repository state is causing a failure?

Why is -X inappropriate as a default CI benchmark mode?

9. Bridge to the checkpoint

Lesson 5 combines the chapter into one acceptance exercise: profile a reactor, select one bounded performance improvement, prove that required work still executes, and compare artifacts built in isolated directories and repositories.

Official references and version notes

Version-sensitive statements in this lesson were checked against current Apache Maven primary documentation on 2026-08-24.

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.