Chapter 11Lesson 04~165 minutes

Maven dependencyManagement, BOMs, Version Alignment, Enforcer, and Dependency Analysis: Diagnostics, Failure Modes, Security, and Performance

Diagnose dependency-governance failures from effective model and graph evidence: missing declarations, malformed BOM imports, precedence surprises, over-strict Enforcer policy, and analysis false positives caused by reflection.

DiagnosticsBOM PrecedenceEnforcerReflectionClean-Room

Most dependency-governance failures are not solved by deleting caches or adding random versions until Maven becomes green. The reliable sequence is evidence first: effective model, dependency graph, policy rule, then the smallest correction that makes the intent explicit.

Learning objectives

  • Diagnose a managed-but-not-declared dependency correctly.
  • Recognize malformed BOM imports and duplicate-BOM precedence.
  • Interpret Enforcer failures without weakening policy blindly.
  • Distinguish real unused dependencies from bytecode-analysis false positives.
  • Use isolated repository state to separate graph problems from cache effects.
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, Maven Dependency Plugin 3.11.0, Maven Enforcer Plugin 3.6.3, Help Plugin 3.5.2, and Compiler Plugin 3.15.0. All Maven resolution uses a disposable project-local repository. No normal ~/.m2, global settings, production CI, or real artifact repository is modified.

1. Evidence-first diagnostic sequence

Use the same production sequence throughout this course:

  1. Preserve concise command/error evidence.
  2. Confirm wrapper, Maven, and JDK identity.
  3. Inspect declared and effective POMs.
  4. Inspect the resolved dependency/reactor graph.
  5. Inspect repository/cache/filesystem state only when resolution evidence points there.
  6. Identify the exact Enforcer or analysis rule that failed.
  7. Apply the least destructive correction.
  8. Rebuild in controlled isolated state and verify the intended invariant.

2. Failure: “It is in dependencyManagement, so why can’t I import the class?”

Symptom: compilation reports a missing package even though the coordinate appears in the parent or BOM. The mistake is conceptual: management supplied no edge. Check the child <dependencies> and dependency tree. If the source uses the API directly, declare the dependency without repeating the managed version.

<dependencies>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
  </dependency>
</dependencies>

3. Intentionally broken example: BOM import is shaped like an ordinary dependency

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>dev.academy.governance</groupId>
      <artifactId>academy-platform-bom</artifactId>
      <version>1.0.0</version>
      <!-- BROKEN ON PURPOSE: type=pom and scope=import are missing. -->
    </dependency>
  </dependencies>
</dependencyManagement>

This entry sits in dependencyManagement but lacks type=pom and scope=import. It does not import the BOM's managed list. A versionless child dependency can therefore fail model validation with a “dependency.version is missing” style error.

set -euo pipefail
./mvnw -Dmaven.repo.local=.diag-m2 -f apps/app-a/pom.xml help:effective-pom -Dverbose   > evidence/broken-effective-pom.xml 2> evidence/broken-effective-pom.err || true
grep -n "academy-platform-bom\|commons-lang3\|version.*missing"   evidence/broken-effective-pom.xml evidence/broken-effective-pom.err || true

Repair only the import shape. Do not add duplicated versions to every child; that hides the missing platform policy instead of fixing it.

4. Failure: duplicate BOMs produce a surprising version

Symptom: the effective POM contains a version different from the one an engineer expected after importing two platforms. First inspect the imports in source order and the effective dependencyManagement. Maven's documented BOM example demonstrates first-import precedence for a duplicate managed coordinate when the current POM does not define that coordinate itself.

Correction options: remove accidental duplicate management, reorder imports only when precedence is truly the policy, or add an explicit managed override in the consuming platform/POM. Record why the override exists so a future BOM upgrade does not look like dead configuration.

5. Failure: Enforcer blocks a build that “used to work”

A green build before Enforcer means Maven could resolve a graph, not that the graph satisfied your new governance invariant. For dependencyConvergence, capture every reported path. Identify whether the difference is an accidental stale version, a legitimate ecosystem incompatibility, or a deliberately isolated scope.

Do not immediately add a broad exclusion to the rule. The rule supports filtering, but a policy exception should be narrower than the graph problem and should carry a reason. Otherwise a temporary compatibility escape becomes permanent invisible debt.

set -euo pipefail
./mvnw -Dmaven.repo.local=.diag-m2 -f apps/pom.xml   org.apache.maven.plugins:maven-dependency-plugin:3.11.0:tree   -Dincludes=org.apache.commons:commons-lang3   | tee evidence/lang3-tree.txt

6. Failure: dependency analysis calls reflective usage “unused”

App B loads StringUtils through Class.forName. The class name is a string, not a normal static type reference. The current Dependency Plugin documents that its default analyzer works at bytecode level and exposes usedDependencies specifically to override incomplete results.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <configuration>
    <usedDependencies>
      <usedDependency>org.apache.commons:commons-lang3</usedDependency>
    </usedDependencies>
  </configuration>
</plugin>
Do not use this as a blanket suppression. Verify the reflective/service-loader/resource-based runtime path with a test or executable evidence, then record the narrow coordinate and reason.

7. Repository/cache evidence without destructive cleanup

If a local installed BOM masks a missing repository publication or an old POM seems to persist, first switch to a fresh disposable repository path such as .diag-m2-fresh. If the failure appears only there, you have evidence about state dependence. Do not delete the normal Maven repository as a first diagnostic step.

set -euo pipefail
./mvnw -Dmaven.repo.local=.diag-m2-a -f platform-bom/pom.xml install
./mvnw -Dmaven.repo.local=.diag-m2-a -f apps/pom.xml verify   | tee evidence/warm-policy.log
# A different empty repository has no installed local BOM yet.
./mvnw -Dmaven.repo.local=.diag-m2-b -f apps/pom.xml validate   > evidence/fresh-without-bom.log 2>&1 || true

The second failure is expected: it proves that the separate apps build has a declared dependency on the BOM artifact being available. The correct setup step is to publish/install the BOM into the controlled repository, not to weaken the import.

8. Performance: measure the right layer

Dependency governance adds model processing, graph traversal, and static analysis. A cold build also downloads POMs/JARs/plugins, so do not attribute all latency to Enforcer. Compare cold and warm resolution, then isolate rule and analysis time from compilation/tests. In CI, upload concise tree/gate evidence on failure rather than enabling permanent debug logging.

Knowledge check

A child sees a managed version but compilation says the package is missing. What is the first model question?

What is the least destructive test for suspected stale Maven repository state?

Why is an Enforcer failure after adding dependencyConvergence not proof that Maven resolution is broken?

How should a reflection-related dependency:analyze false positive be handled?

What should you inspect when two imported BOMs manage the same coordinate unexpectedly?

9. Summary and bridge

Governance failures become tractable when you keep declaration, management, resolved graph, policy rules, and static-analysis evidence separate. Lesson 5 combines all five layers in one checkpoint with a deliberate divergence and a deliberate reflection false positive.

Official references and version notes

Version-sensitive statements were checked against Apache Maven primary documentation on 2026-08-23. The mandatory path pins Maven 3.9.16 via Maven Wrapper 3.3.4, JDK 21 to run Maven, Java 17 as the compiler release target, Maven Dependency Plugin 3.11.0, Maven Enforcer Plugin 3.6.3, Help Plugin 3.5.2, and Compiler Plugin 3.15.0.

The illustrative dependency family deliberately uses org.apache.commons:commons-text:1.10.0 and org.apache.commons:commons-lang3:3.12.0 because it produces a small, stable graph for explaining management and convergence. These are teaching pins, not claims that the versions are the newest releases.

The intentionally broken examples operate only on disposable POMs and isolated repositories. No production exception, remote repository, or credential is changed.

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.