Chapter 11Lesson 01~145 minutes

Maven dependencyManagement, BOMs, Version Alignment, Enforcer, and Dependency Analysis: Concepts, Architecture, and Mental Model

Separate Maven dependency declarations from version policy, then connect imported BOMs, dependency convergence rules, upper-bound checks, and bytecode-based dependency analysis to the resolved dependency graph.

dependencyManagementBOMVersion PolicyEnforcerAnalysis

Chapter 06 made dependency edges and classpaths visible; Chapters 07–10 made plugin, environment, reactor, and test behavior explicit. The next scaling problem is policy: when twenty modules need the same libraries, where should versions live, and how can CI prove that the resolved graph still obeys the policy?

Learning objectives

  • Distinguish a dependency declaration from a dependencyManagement policy entry.
  • Explain how an imported BOM contributes managed coordinates without adding classpath edges.
  • Compare dependencyConvergence and requireUpperBoundDeps as different graph policies.
  • Explain what dependency:analyze can and cannot prove from bytecode.
  • Inspect effective dependency policy before changing the build.
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. The practical problem: policy is not the graph

In a small project, repeating a version in every <dependency> can look harmless. In a large repository it creates drift: one module updates, another does not, a third receives a different transitive version, and the build remains green only because Maven mediation happens to select something compatible.

Maven therefore separates two concerns. Declaration says “this project needs this dependency,” creating a graph edge. Management says “if this coordinate appears, this is the version/scope/exclusion policy to use.” Management is a policy table; it does not itself add the library to the compile or runtime classpath.

2. Mental model: declaration, management, import, resolution, enforcement

Policy becomes meaningful only when a dependency edge is resolved
flowchart TD
  P[Project POM] --> D[dependencies: graph edges]
  P --> M[dependencyManagement: policy]
  B[BOM POM] -->|type=pom + scope=import| M
  D --> R[Resolved dependency graph]
  M --> R
  R --> C[Compile/runtime classpaths]
  R --> E[Enforcer policy checks]
  C --> A[dependency:analyze bytecode evidence]

The arrows are deliberately different. A declaration enters resolution as an edge. A BOM import enters the management table. Maven combines them when it resolves versions. Enforcer evaluates graph/model rules during the build. The Dependency Plugin can then compare compiled bytecode usage with declared dependencies, but that analysis is not equivalent to runtime tracing.

3. dependencyManagement is a policy table, not an include list

A managed entry is matched by dependency identity (normally groupId:artifactId for an ordinary JAR without classifier). It can supply a version when a child declaration omits one and can control versions encountered transitively. Maven documentation is explicit that dependency management takes precedence over ordinary transitive mediation for project dependencies.

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

This fragment alone does not place Commons Lang on any classpath. A child still needs a dependency edge, directly or transitively. That distinction is one of the most important Maven governance invariants.

4. BOM import: reusable management without parent inheritance

A Bill of Materials (BOM) is normally a POM-packaged artifact whose dependencyManagement defines a coordinated family of versions. A consumer imports that managed set by declaring the BOM inside its own dependencyManagement with <type>pom</type> and <scope>import</scope>.

<dependency>
  <groupId>dev.academy.governance</groupId>
  <artifactId>academy-platform-bom</artifactId>
  <version>1.0.0</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>
Maven 4 boundary: Maven 4 introduces a dedicated bom packaging for the newer 4.1.0 model. This course remains on the Maven 3.9.16 production path, so the mandatory labs use the long-established pom packaging plus type=pom/scope=import.

5. Precedence: local policy, parents, and multiple imports

Policy can arrive from the current POM, its parent, and imported BOMs. A declaration in the current POM's dependencyManagement can override inherited management. When multiple imported BOMs manage the same coordinate and the consuming POM does not manage it directly, Maven's documented example shows that the BOM imported first can determine the version. That is why import order is reviewable policy, not formatting noise.

Situation What to inspect Why
Child omits version Effective POM + dependency tree Proves which management entry supplied the selected version.
Current POM manages same coordinate as parent Current dependencyManagement Current POM management takes precedence.
Two BOMs manage same coordinate Import order + effective POM A duplicate managed coordinate can resolve according to import precedence.
Plugin dependency differs Plugin configuration/dependency graph Project dependencyManagement does not universally govern plugin transitive dependencies.

6. Enforcer: convergence and upper-bound policies answer different questions

dependencyConvergence asks whether the same dependency appears at different versions anywhere in the graph paths covered by the rule. A mediated build can therefore run with one selected version and still fail convergence because conflicting requests exist in the graph.

requireUpperBoundDeps is narrower: it checks that the resolved version is at least the highest version requested by the dependency paths, subject to the rule's comparison semantics. It can tolerate some non-convergent graphs that still select the highest requested version. Neither rule “fixes” dependencies; each rejects a graph that violates its policy.

Policy Question Typical use
dependencyConvergence Are all paths asking for the same version? Strict graph consistency and explainability.
requireUpperBoundDeps Did resolution choose a version lower than another requested version? Prevent accidental downgrades caused by nearest-wins mediation.
No Enforcer graph rule Can Maven resolve something? Too weak as a production governance criterion by itself.

7. Dependency analysis is bytecode evidence, not runtime omniscience

dependency:analyze classifies dependencies as used-and-declared, used-but-undeclared, or unused-and-declared. In 3.11.0 the default analyzer works at bytecode level. That is valuable, but reflection, service loading, generated code, framework discovery, native integrations, and resource-based lookups can escape ordinary static references.

The plugin therefore exposes targeted mechanisms such as usedDependencies to document a known dependency as used when bytecode analysis cannot see the relationship. A false positive should produce an explicit, reviewed exception—not a habit of disabling analysis.

8. Read-only inspection before mutation

Cross-platform note: POSIX examples use ./mvnw, grep, find, and sha256sum. On Windows use mvnw.cmd, Select-String, Get-ChildItem, and Get-FileHash. Maven dependency-management and Enforcer semantics are cross-platform; shell quoting and path syntax are not.
set -euo pipefail
./mvnw -v
java -version
mkdir -p evidence
./mvnw -Dmaven.repo.local=.lab-m2 help:effective-pom -Dverbose   > evidence/effective-pom.xml
./mvnw -Dmaven.repo.local=.lab-m2 org.apache.maven.plugins:maven-dependency-plugin:3.11.0:tree   > evidence/dependency-tree.txt
./mvnw -Dmaven.repo.local=.lab-m2 org.apache.maven.plugins:maven-dependency-plugin:3.11.0:analyze   > evidence/dependency-analysis.txt

help:effective-pom answers “what management/configuration is active?” The tree answers “what graph was resolved?” Analysis answers “what compiled bytecode appears to use?” Keep those evidence types separate.

9. DevOps and supply-chain boundary

Central version policy reduces drift, but it also centralizes power. A BOM update can change hundreds of classpaths; an Enforcer exception can weaken a gate across many modules; and a parent POM can make policy invisible to a casual reader. Treat parent/BOM changes like executable delivery-policy changes: pin coordinates, review diffs, run clean-room resolution, retain tree evidence, and avoid dynamic versions as production defaults.

Knowledge check

Does dependencyManagement add a library to a child classpath?

What two elements make a Maven 3 BOM import an import rather than an ordinary POM dependency?

Why can dependencyConvergence fail even when Maven selected exactly one JAR version?

Why can dependency:analyze report a real runtime dependency as unused?

Does a BOM constitute a universal Maven lockfile?

10. Summary and bridge

Dependency declarations create graph edges; dependencyManagement and imported BOMs supply version policy. Enforcer makes chosen graph invariants executable, while dependency analysis checks declaration quality from compiled evidence with known limits. Lesson 2 turns those distinctions into a small local platform BOM and two governed modules.

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.

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.