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.
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.
~/.m2,
global settings, production CI, or real artifact repository is
modified.
1. Evidence-first diagnostic sequence
Use the same production sequence throughout this course:
- Preserve concise command/error evidence.
- Confirm wrapper, Maven, and JDK identity.
- Inspect declared and effective POMs.
- Inspect the resolved dependency/reactor graph.
- Inspect repository/cache/filesystem state only when resolution evidence points there.
- Identify the exact Enforcer or analysis rule that failed.
- Apply the least destructive correction.
- 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>
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?
Whether the child actually declares the dependency edge; dependencyManagement alone does not add it.
What is the least destructive test for suspected stale Maven repository state?
Run the same operation with a new isolated local repository path, leaving normal ~/.m2 untouched.
Why is an Enforcer failure after adding dependencyConvergence not proof that Maven resolution is broken?
Maven may resolve one version successfully while the new rule intentionally rejects the existence of conflicting version requests in the graph.
How should a reflection-related dependency:analyze false positive be handled?
Verify the runtime mechanism, document a narrow usedDependencies/ignore exception, and keep the rest of the analysis gate active.
What should you inspect when two imported BOMs manage the same coordinate unexpectedly?
Source import order, effective dependencyManagement, and any current-POM managed override.
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.
- Maven — Introduction to the Dependency Mechanism
- Maven Dependency Plugin 3.11.0 — Introduction
- Maven Dependency Plugin 3.11.0 — dependency:analyze
- Maven Dependency Plugin 3.11.0 — dependency:analyze-only
- Maven Enforcer Plugin 3.6.3 — Introduction
- Enforcer Rule — dependencyConvergence
- Enforcer Rule — requireUpperBoundDeps
- Maven Help Plugin 3.5.2
- Maven Compiler Plugin 3.15.0
- 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.