Maven Dependency Analysis & Convergence

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Maven 3.10.0 with the Apache Maven Dependency Plugin 3.11.0 and the Apache Maven Enforcer Plugin 3.6.3; the third-party MojoHaus Versions Maven Plugin 2.23.0 is covered at the end. Every example was run on JDK 26 on 2026-10-16. Where the output is identical on Maven 3.9.16 and Maven 4.0.0-rc-7 (a release candidate) the text says so; where it differs the difference is named. Paths in the console output were shortened to /work/mvn-dep-analysis/..., and the one line Loaded N auto-discovered prefixes for remote repository ... that Maven 3.10 prints while resolving was left out.

A Maven dependency graph is computed, not written: the build you read in the POM and the build that actually runs differ by every transitive dependency, every mediation decision and every management override. Declaring a dependency is easy; knowing what you really got, whether you still use it, and whether two libraries agree about a third one needs tools. This page collects them. How the graph is built (scopes, nearest-wins mediation, exclusions) is explained on Dependencies, Scopes & Mediation; dependencyManagement and BOMs are on Dependency Management & BOMs; version syntax and snapshots are on Versions, Snapshots & Ranges; the enforcer plugin as a whole is on The Enforcer Plugin. The cross-tool vocabulary is in Dependency Management Fundamentals.

Two example projects

Two scratch projects are used. The first is the multi-module tidepool build (tidepool-model, tidepool-engine, tidepool-cli, a JUnit Jupiter 6.1.3 test in the engine, Commons CSV 1.14.1 and picocli 4.7.7), with the dependency plugin pinned to 3.11.0 in pluginManagement. The second is a small module, tidepool-report, that is deliberately inconsistent:

<dependencies>
  <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
  </dependency>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-csv</artifactId>
    <version>1.14.1</version>
  </dependency>
</dependencies>

Both libraries need Commons IO, but Commons FileUpload 1.5 asks for 2.11.0 and Commons CSV 1.14.1 asks for 2.20.0. The rest of the page is largely about finding, explaining and ending that disagreement. Both projects are this page’s own lab setup, not examples from the Maven documentation.

graph TD R["tidepool-report 1.0.0"] --> F["commons-fileupload 1.5"] R --> C["commons-csv 1.14.1"] F --> IO11["commons-io 2.11.0 - selected, first declared"] C -.-> IO20["commons-io 2.20.0 - omitted for conflict"] C --> CD["commons-codec 1.19.0"]

Seeing the graph: dependency:tree

dependency:tree prints the resolved dependency hierarchy of the project that is being built, the one Maven actually uses. For the tidepool CLI module (mvn -pl tidepool-cli dependency:tree):

[INFO] --- dependency:3.11.0:tree (default-cli) @ tidepool-cli ---
[INFO] io.tidepool:tidepool-cli:jar:1.0.0
[INFO] +- io.tidepool:tidepool-engine:jar:1.0.0:compile
[INFO] |  +- io.tidepool:tidepool-model:jar:1.0.0:compile
[INFO] |  \- org.apache.commons:commons-csv:jar:1.14.1:compile
[INFO] |     +- commons-io:commons-io:jar:2.20.0:compile
[INFO] |     \- commons-codec:commons-codec:jar:1.19.0:compile
[INFO] +- info.picocli:picocli:jar:4.7.7:compile
[INFO] \- org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[INFO]    +- org.junit.jupiter:junit-jupiter-api:jar:6.1.3:test
[INFO]    |  +- org.opentest4j:opentest4j:jar:1.3.0:test
[INFO]    |  +- org.junit.platform:junit-platform-commons:jar:6.1.3:test
[INFO]    |  +- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO]    |  \- org.jspecify:jspecify:jar:1.0.0:test
[INFO]    +- org.junit.jupiter:junit-jupiter-params:jar:6.1.3:test
[INFO]    \- org.junit.jupiter:junit-jupiter-engine:jar:6.1.3:test
[INFO]       \- org.junit.platform:junit-platform-engine:jar:6.1.3:test

Each line is groupId:artifactId:type[:classifier]:version:scope; the indentation is the dependency path. The module’s own dependencies are the first level; tidepool-model appears only below tidepool-engine, which matters in the next section.

Parameters

Parameter User property Default / since Meaning

verbose

verbose

false, since 2.0-alpha-6

Include the nodes that mediation omitted, with the reason (see below).

includes

includes

since 2.0-alpha-6

Comma-separated filter: only the matching artifacts, and the paths that lead to them, are kept. Syntax [groupId]:[artifactId]:[type]:[version]; every segment is optional and may use * wildcards, so org.apache.* matches by group prefix and :::*-SNAPSHOT matches snapshots.

excludes

excludes

since 2.0-alpha-6

The same syntax, but the matches are removed from the tree.

scope

scope

since 2.0-alpha-5

Resolve the tree for one scope (for example runtime) instead of all of them.

outputType

outputType

text, since 2.2

text, dot, graphml, tgf, and json (since 3.7.0).

outputFile

outputFile

since 2.0-alpha-5

Write the tree to a file instead of the console.

appendOutput

appendOutput

false, since 2.2

Append to outputFile instead of overwriting it (useful with a multi-module build).

tokens

tokens

standard, since 2.0-alpha-6

Line-drawing characters: whitespace, standard (ASCII) or extended.

outputEncoding

outputEncoding

${project.reporting.outputEncoding}

Character set of outputFile.

skip

skip

false, since 2.7

Do nothing.

Why is this version here? -Dverbose

By default the tree shows only the winners. With -Dverbose the losers of mediation appear in parentheses, with the reason. On tidepool-report:

[INFO] --- dependency:3.11.0:tree (default-cli) @ tidepool-report ---
[INFO] io.tidepool:tidepool-report:jar:1.0.0
[INFO] +- commons-fileupload:commons-fileupload:jar:1.5:compile
[INFO] |  \- commons-io:commons-io:jar:2.11.0:compile
[INFO] \- org.apache.commons:commons-csv:jar:1.14.1:compile
[INFO]    +- (commons-io:commons-io:jar:2.20.0:compile - omitted for conflict with 2.11.0)
[INFO]    \- commons-codec:commons-codec:jar:1.19.0:compile

The plain tree shows only commons-io:2.11.0 below Commons FileUpload and nothing below Commons CSV except Commons Codec; the verbose tree shows that Commons CSV wanted 2.20.0 and lost. Both versions sit at depth 2, so the tie is broken by declaration order: Commons FileUpload is declared first, so its Commons IO wins. Commons CSV 1.14.1 will run against a Commons IO two minor lines older than the one it was built and tested with. The three Maven releases print exactly the same verbose tree: the output above was identical on 3.10.0, 3.9.16 and 4.0.0-rc-7.

Finding one artifact: -Dincludes

On a real project the tree has hundreds of lines. -Dincludes keeps only the paths that lead to the artifact you ask about:

$ mvn -pl tidepool-cli dependency:tree -Dincludes=org.apache.commons,commons-io
[INFO] --- dependency:3.11.0:tree (default-cli) @ tidepool-cli ---
[INFO] io.tidepool:tidepool-cli:jar:1.0.0
[INFO] \- io.tidepool:tidepool-engine:jar:1.0.0:compile
[INFO]    \- org.apache.commons:commons-csv:jar:1.14.1:compile
[INFO]       \- commons-io:commons-io:jar:2.20.0:compile

-Dincludes=... takes a comma-separated list, and each entry is a full filter pattern, so commons-io matches by group and org.apache.commons matches the Commons CSV group. The complementary -Dexcludes removes noise, such as the whole JUnit subtree, when it is the test scope you do not care about; -Dscope=runtime does it more simply by not resolving test dependencies at all.

Output formats

-DoutputType renders the same tree for other tools. dot is a Graphviz digraph; tgf is Trivial Graph Format (a node list, a # line, then the edges); graphml and json (3.7.0) are for graph viewers and scripts. For the runtime scope of the CLI:

$ mvn -pl tidepool-cli dependency:tree -Dscope=runtime -DoutputType=dot
[INFO] digraph "io.tidepool:tidepool-cli:jar:1.0.0" {
[INFO] 	"io.tidepool:tidepool-cli:jar:1.0.0" -> "io.tidepool:tidepool-engine:jar:1.0.0:compile" ;
[INFO] 	"io.tidepool:tidepool-cli:jar:1.0.0" -> "info.picocli:picocli:jar:4.7.7:compile" ;
[INFO] 	"io.tidepool:tidepool-engine:jar:1.0.0:compile" -> "io.tidepool:tidepool-model:jar:1.0.0:compile" ;
[INFO] 	"io.tidepool:tidepool-engine:jar:1.0.0:compile" -> "org.apache.commons:commons-csv:jar:1.14.1:compile" ;
[INFO] 	"org.apache.commons:commons-csv:jar:1.14.1:compile" -> "commons-io:commons-io:jar:2.20.0:compile" ;
[INFO] 	"org.apache.commons:commons-csv:jar:1.14.1:compile" -> "commons-codec:commons-codec:jar:1.19.0:compile" ;
[INFO]  }

(Trailing spaces on the console lines were trimmed.) Add -DoutputFile=graph.dot to write the graph without the [INFO] prefix. With -DoutputType=tgf the same graph is printed as seven numbered nodes followed by # and six from to scope edges, each node number being a hash of the coordinates.

Auditing the declarations: dependency:analyze

The tree shows what you got. dependency:analyze asks whether the POM declares what the code uses. It reads the compiled bytecode of your classes and tests with the Maven Dependency Analyzer, computes which dependency JARs the bytecode refers to, and compares that with the declared dependencies. It reports three groups:

  • Used undeclared: the code uses a class from a JAR that arrives only transitively. If the library that brought it drops it, your build breaks.

  • Unused declared: a declared dependency that no class refers to. Dead weight, or a runtime-only dependency the analysis cannot see.

  • Non-test scoped, test only: a compile-scope dependency that only test classes use (should be test scope).

analyze, analyze-only, analyze-report and analyze-duplicate

Goal Purpose

dependency:analyze

For the command line. It always forks the lifecycle up to test-compile first, so there are classes to analyse (since 2.0-alpha-3).

dependency:analyze-only

The same analysis for binding in the POM; it assumes test-compile already ran. Default phase verify (since 2.0).

dependency:analyze-report

The same results as a report for mvn site, configured in <reporting> (not run here).

dependency:analyze-dep-mgt

Compares resolved versions with dependencyManagement (next sections).

dependency:analyze-exclusions

Finds exclusions that no longer exclude anything (since 3.7.0).

dependency:analyze-duplicate

Finds dependencies declared twice in <dependencies> or <dependencyManagement>.

Run over the whole tidepool reactor (output reduced to the plugin lines and warnings; the compiler and resources lines of the forked test-compile were left out):

$ mvn dependency:analyze
[INFO] --- dependency:3.11.0:analyze (default-cli) @ tidepool ---
[INFO] Skipping pom project
[INFO] --- dependency:3.11.0:analyze (default-cli) @ tidepool-model ---
[WARNING] Unused declared dependencies found:
[WARNING]    org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[INFO] --- dependency:3.11.0:analyze (default-cli) @ tidepool-engine ---
[WARNING] Used undeclared dependencies found:
[WARNING]    org.junit.jupiter:junit-jupiter-api:jar:6.1.3:test
[WARNING] Unused declared dependencies found:
[WARNING]    org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[INFO] --- dependency:3.11.0:analyze (default-cli) @ tidepool-cli ---
[WARNING] Used undeclared dependencies found:
[WARNING]    io.tidepool:tidepool-model:jar:1.0.0:compile
[WARNING] Unused declared dependencies found:
[WARNING]    org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[INFO] BUILD SUCCESS

Three real findings, and one that is not a finding:

  • tidepool-cli uses io.tidepool.model.Station but declares only tidepool-engine; tidepool-model arrives through the engine. This is the textbook used undeclared case and it is a true positive.

  • tidepool-model and tidepool-cli have no tests, so junit-jupiter is truly unused there. The parent POM declares it for every module, which is the cause.

  • tidepool-engine has a test that imports org.junit.jupiter.api.Test. The declared artifact is the aggregator junit-jupiter, which contains no classes of its own; the classes live in junit-jupiter-api, so the analysis reports the aggregator as unused and the API artifact as used but undeclared. This is a false positive produced by how the JUnit aggregator is packaged, and the usual reason for the ignore settings below.

The same warnings, in the same order, were produced by Maven 3.9.16 and by 4.0.0-rc-7. (4.0.0-rc-7 additionally prints Unable to find the root directory. Create a .mvn directory... when the project has no .mvn directory.)

Seeing what is used: -Dverbose, -DoutputXML, -DscriptableOutput

-Dverbose also prints the used declared dependencies, and under each used undeclared dependency the classes that were found:

$ mvn -pl tidepool-cli dependency:analyze -Dverbose
[INFO] --- dependency:3.11.0:analyze (default-cli) @ tidepool-cli ---
[INFO] Used declared dependencies found:
[INFO]    io.tidepool:tidepool-engine:jar:1.0.0:compile
[INFO]    info.picocli:picocli:jar:4.7.7:compile
[WARNING] Used undeclared dependencies found:
[WARNING]    io.tidepool:tidepool-model:jar:1.0.0:compile
[WARNING]       class io.tidepool.model.Height
[WARNING]       class io.tidepool.model.Station
[WARNING] Unused declared dependencies found:
[WARNING]    org.junit.jupiter:junit-jupiter:jar:6.1.3:test

(The two trailing Ignored ... groups that -Dverbose adds were omitted; with no ignore settings they repeat the warnings.) -DoutputXML prints the <dependency> element to paste for the missing dependencies, and -DscriptableOutput prints machine-readable lines starting with a marker (-DscriptableFlag, default $$$%%%) for scripts:

$ mvn -pl tidepool-cli dependency:analyze -DoutputXML
...
[INFO] Add the following to your pom to correct the missing dependencies:
[INFO]
<dependency>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-model</artifactId>
  <version>1.0.0</version>
</dependency>

Failing the build

By default the report is only advice. -DfailOnWarning=true (or <failOnWarning> in the POM) turns it into an error:

$ mvn -pl tidepool-cli dependency:analyze -DfailOnWarning
[ERROR] Used undeclared dependencies found:
[ERROR]    io.tidepool:tidepool-model:jar:1.0.0:compile
[ERROR] Unused declared dependencies found:
[ERROR]    org.junit.jupiter:junit-jupiter:jar:6.1.3:test
[ERROR] BUILD FAILURE
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.11.0:analyze (default-cli) on project tidepool-cli: Dependency problems found -> [Help 1]

For a permanent gate bind analyze-only to verify, and ignore the known false positives. The configuration below was added to the parent POM; the two ignore lists name the aggregator and its API (the pattern is groupId:artifactId, with the same optional type:version tail and * wildcards as the other filters):

<build>
  <plugins>
    <plugin>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>analyze</id>
          <goals>
            <goal>analyze-only</goal>
          </goals>
          <configuration>
            <failOnWarning>true</failOnWarning>
            <ignoredUnusedDeclaredDependencies>
              <ignoredUnusedDeclaredDependency>org.junit.jupiter:junit-jupiter</ignoredUnusedDeclaredDependency>
            </ignoredUnusedDeclaredDependencies>
            <ignoredUsedUndeclaredDependencies>
              <ignoredUsedUndeclaredDependency>org.junit.jupiter:junit-jupiter-api</ignoredUsedUndeclaredDependency>
            </ignoredUsedUndeclaredDependencies>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

With the fault in tidepool-cli still there, mvn verify stops exactly at the real problem and the false positives are silent:

[INFO] --- dependency:3.11.0:analyze-only (analyze) @ tidepool-cli ---
[ERROR] Used undeclared dependencies found:
[ERROR]    io.tidepool:tidepool-model:jar:1.0.0:compile
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for tidepool 1.0.0:
[INFO]
[INFO] tidepool ........................................... SUCCESS [  1.186 s]
[INFO] tidepool-model ..................................... SUCCESS [  1.026 s]
[INFO] tidepool-engine .................................... SUCCESS [  0.919 s]
[ERROR] tidepool-cli ....................................... FAILURE [  0.085 s]
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.11.0:analyze-only (analyze) on project tidepool-cli: Dependency problems found -> [Help 1]

The fix is one command, described in Editing the POM from the command line: add and remove; afterwards the same mvn verify prints BUILD SUCCESS and the analyze-only (analyze) execution is silent in all four modules.

All analyze parameters

Parameter User property Default / since Meaning

failOnWarning

failOnWarning

false

Fail the build if any warning is found.

verbose

verbose

false

Also list the used dependencies, and the classes that make a dependency used.

outputXML

outputXML

false, since 2.0-alpha-5

Print the XML for the used-undeclared dependencies.

scriptableOutput, scriptableFlag

scriptableOutput, scriptableFlag

false, $$$%%%; since 2.0-alpha-5

Print machine-readable lines for the used-undeclared dependencies, starting with the flag.

ignoredDependencies

-

since 2.10

Dependencies excluded from all three lists (unused declared, used undeclared, non-test scoped).

ignoredUnusedDeclaredDependencies

-

since 2.10

Dependencies not reported when declared but unused.

ignoredUsedUndeclaredDependencies

-

since 2.10

Dependencies not reported when used but undeclared.

ignoredNonTestScopedDependencies

-

since 3.3.0; default org.slf4j:slf4j-simple::

Dependencies not reported when used only by tests but not test-scoped.

ignoreAllNonTestScoped

ignoreAllNonTestScoped

false, since 3.3.1

Ignore the whole non-test-scoped group.

ignoreUnusedRuntime

ignoreUnusedRuntime

false, since 3.2.0

Never report runtime dependencies as unused.

ignoreNonCompile

ignoreNonCompile

false

Do not report runtime, provided, test or system dependencies as unused.

usedDependencies

-

since 2.6

Force groupId:artifactId pairs to count as used, to cover what bytecode analysis cannot see.

excludedClasses

mdep.analyze.excludedClasses

since 3.7.0

Regular expressions on full class names; matching classes are not analysed (for generated code).

ignoredPackagings

-

pom,ear, since 3.2.1

Packagings that are skipped.

analyzer

analyzer

default, since 2.2

Plexus role hint of another analyzer implementation, supplied as a plugin dependency.

skip

mdep.analyze.skip

false, since 2.7

Do nothing.

The plugin does not warn about a few dependencies where bytecode analysis is known to be unreliable, SLF4J being the example the documentation gives. Two causes of false positives are named in the documentation: annotations with source retention, which do not appear in bytecode, and classes loaded by reflection at runtime. For those use usedDependencies or the ignore lists. Because the analysis is on bytecode, it cannot see a dependency that is only needed at runtime (a JDBC driver, a logging backend); declare those with runtime scope and add ignoreUnusedRuntime.

Management overrides: dependency:analyze-dep-mgt

Maven applies dependencyManagement to transitive dependencies, so a managed version always wins there. A mismatch can only remain where a direct declaration overrides the managed version, which is why the goal ignores direct dependencies by default (ignoreDirect=true, user property mdep.analyze.ignore.direct). In a copy of tidepool-report the management section pins Commons IO 2.20.0 while a direct declaration asks for 2.11.0:

$ mvn -f pom-mgt.xml dependency:analyze-dep-mgt
[INFO] --- dependency:3.11.0:analyze-dep-mgt (default-cli) @ tidepool-report ---
[INFO] Found Resolved Dependency/DependencyManagement mismatches:
[INFO] 	Ignoring Direct Dependencies.
[INFO] 	None

$ mvn -f pom-mgt.xml dependency:analyze-dep-mgt -Dmdep.analyze.ignore.direct=false
[INFO] --- dependency:3.11.0:analyze-dep-mgt (default-cli) @ tidepool-report ---
[INFO] Found Resolved Dependency/DependencyManagement mismatches:
[INFO] 	Dependency: commons-io:commons-io:jar
[INFO] 		DepMgt  : 2.20.0
[INFO] 		Resolved: 2.11.0
[WARNING] Potential problems found in Dependency Management

-Dmdep.analyze.failBuild=true turns the warning into Failed to execute goal ...:analyze-dep-mgt (default-cli) on project tidepool-report: Found Dependency errors. Use the goal to keep a parent’s or BOM’s management section honest: a child that silently declares its own version defeats the point of managing versions.

Stale exclusions: dependency:analyze-exclusions

An <exclusion> records a decision ("do not bring me X through this dependency"). When the dependency is later upgraded and stops bringing X, the exclusion silently becomes noise. Since 3.7.0 analyze-exclusions finds those. Here Commons FileUpload excludes Commons IO (effective) and Commons Logging (which Commons FileUpload 1.5 does not depend on):

$ mvn -f pom-excl.xml dependency:analyze-exclusions
[INFO] --- dependency:3.11.0:analyze-exclusions (default-cli) @ tidepool-report ---
[WARNING] tidepool-report defines following unnecessary excludes
[WARNING]     commons-fileupload:commons-fileupload:1.5
[WARNING]         - commons-logging:commons-logging @ line: 26

The report carries the POM line number. With -Dmdep.exclusion.fail=true (exclusionFail, since 3.7.0) the same lines are logged at ERROR level and the build fails with Invalid exclusions found. Note what the tree looks like after the effective exclusion: Commons IO 2.20.0 is now taken from Commons CSV, so Commons FileUpload 1.5 runs with a newer Commons IO than it was built against. Exclusions change the runtime of the excluded-from library; run its tests.

Duplicate declarations: dependency:analyze-duplicate

analyze-duplicate lists a dependency declared twice in the same POM. On Maven 3.9.16, which only warns about it:

$ mvn -f pom-dup.xml dependency:analyze-duplicate
[WARNING] Some problems were encountered while building the effective model for io.tidepool:tidepool-report:jar:1.0.0
[WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-csv:jar -> duplicate declaration of version 1.14.1 @ line 27, column 17
...
[INFO] --- dependency:3.11.0:analyze-duplicate (default-cli) @ tidepool-report ---
[INFO] List of duplicate dependencies defined in <dependencies/> in your pom.xml:
	o org.apache.commons:commons-csv:jar

On Maven 3.10.0 the goal can no longer be reached for such a POM: the stricter validation introduced in 3.10.0 refuses to build it at all.

[ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-csv:jar -> duplicate declaration of version 1.14.1 @ line 27, column 17
[ERROR] The build could not read 1 project -> [Help 1]

Maven 4.0.0-rc-7 also stops with Encountered 1 problem(s) while processing the POMs. The 3.10.0 release notes describe the change ("duplicated dependency entries or duplicated plugin entries will not be tolerated anymore") and give -Dmaven.resolver.validation=off as a temporary escape hatch (on 3.10.0 the lower-case value fails with No enum constant; the property governs coordinate validation, see Maven: Versions & Release History). The goal is still useful on 3.9.x and as a migration check before upgrading. The enforcer rule banDuplicatePomDependencyVersions covers the sibling problem of the same artifact being declared with different versions through properties.

Acting on the graph

copy-dependencies and build-classpath

dependency:copy-dependencies copies the project’s dependency JARs into a directory (default target/dependency, set with -DoutputDirectory); it binds by default to process-sources and is the usual way to assemble a lib/ directory for a distribution (the alternative is an assembly or shade, see Uber JARs with Shade & Assembly). Runtime-scope dependencies of the CLI:

$ mvn -pl tidepool-cli -am dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/libs
$ ls tidepool-cli/target/libs
commons-codec-1.19.0.jar
commons-csv-1.14.1.jar
commons-io-2.20.0.jar
picocli-4.7.7.jar
tidepool-engine-1.0.0.jar
tidepool-model-1.0.0.jar

$ mvn -pl tidepool-cli -am dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/libs2 -Dmdep.stripVersion=true -DexcludeGroupIds=io.tidepool
$ ls tidepool-cli/target/libs2
commons-codec.jar
commons-csv.jar
commons-io.jar
picocli.jar

The parameters (the same filter family is shared with unpack-dependencies, go-offline and build-classpath):

Parameter User property Meaning

outputDirectory

outputDirectory

Destination; default ${project.build.directory}/dependency.

includeScope, excludeScope

includeScope, excludeScope

Scope threshold, interpreted as Maven filters for creating a classpath (runtime includes compile). A single value, not a list.

includeGroupIds, excludeGroupIds, includeArtifactIds, excludeArtifactIds

same names

Comma-separated lists.

includeTypes, excludeTypes, includeClassifiers, excludeClassifiers

same names

Filter by packaging type or classifier; -Dclassifier=sources finds the sources JAR of every dependency instead (a type default of java-sources).

excludeTransitive

excludeTransitive

Only direct dependencies.

stripVersion, stripClassifier, stripType

mdep.stripVersion, mdep.stripClassifier, mdep.stripType (type since 3.4.0)

Drop the version, classifier or type from the file name.

useRepositoryLayout

mdep.useRepositoryLayout

Write a groupId/artifactId/version repository tree instead of a flat directory.

useSubDirectoryPerArtifact, useSubDirectoryPerType, useSubDirectoryPerScope

mdep.useSubDirectoryPerArtifact and similar

One subdirectory per artifact, per type or per scope.

prependGroupId

mdep.prependGroupId

Avoid name clashes by prefixing the group id.

copyPom, copySignatures, addParentPoms

mdep.copyPom, mdep.copySignatures (3.2.0), mdep.addParentPoms

Copy POMs and .asc signatures too.

overWriteReleases, overWriteSnapshots, overWriteIfNewer

same names

Overwrite rules; overWriteIfNewer defaults to true.

graphRoots

-

since 3.10.0: start from selected groupId/artifactId nodes instead of the project, then apply the filters above.

failOnMissingClassifierArtifact

mdep.failOnMissingClassifierArtifact

With classifier, fail when the classified artifact is missing.

skip, skipDuringIncrementalBuild

mdep.skip

Skip; the second only for IDE incremental builds (3.4.0).

Because all files go to one directory, two dependencies with the same file name overwrite each other. graphRoots is the newest addition. Configured in the plugin as a list of <graphRoot> elements with groupId and artifactId, it restricted the copy of tidepool-report to the Commons CSV subtree, and -DincludeScope=compile then produced:

commons-codec-1.19.0.jar
commons-csv-1.14.1.jar
commons-io-2.20.0.jar
jsr305-3.0.2.jar
spotbugs-annotations-4.9.3.jar

Without graphRoots the same command copies the project’s own four JARs (commons-codec-1.19.0.jar, commons-csv-1.14.1.jar, commons-fileupload-1.5.jar, commons-io-2.11.0.jar). Note that the subtree resolution includes the compile-scope dependencies that the root artifact itself declares and that are not part of the project’s resolved classpath (here two annotation JARs); the output was not analysed further.

dependency:build-classpath prints, or writes with -Dmdep.outputFile=target/cp.txt, the classpath in java -cp form, using the local repository paths. For tidepool-report the file holds four entries separated by the platform separator: commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.jar, commons-io/commons-io/2.11.0/commons-io-2.11.0.jar, org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar and commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar (listed relative to the local repository). dependency:resolve lists the resolved files with their Java module names:

[INFO] --- dependency:3.11.0:resolve (default-cli) @ tidepool-report ---
[INFO] The following files have been resolved:
[INFO]    commons-fileupload:commons-fileupload:jar:1.5:compile -- module commons.fileupload (auto)
[INFO]    commons-io:commons-io:jar:2.11.0:compile -- module org.apache.commons.io [auto]
[INFO]    org.apache.commons:commons-csv:jar:1.14.1:compile -- module org.apache.commons.csv
[INFO]    commons-codec:commons-codec:jar:1.19.0:compile -- module org.apache.commons.codec

list, resolve-sources and resolve-plugins are close relatives. dependency:sources is deprecated in favour of resolve-sources and will be removed.

list-repositories

dependency:list-repositories collects the dependencies and prints the repositories the build and its transitive dependencies' POMs use. The documentation says it shows repositories "defined in your settings, poms, and declared in transitive dependency poms":

[INFO] --- dependency:3.11.0:list-repositories (default-cli) @ tidepool-report ---
[INFO] Project remote repositories used by this build:
 * apache.snapshots (https://repository.apache.org/snapshots, default, snapshots)
 * central (https://repo.maven.apache.org/maven2, default, releases)
 * central (https://repo.maven.apache.org/maven2, default, releases)

apache.snapshots is not in the example POM or in settings.xml. It comes from a transitive dependency’s parent chain: the Commons CSV POM inherits from commons-parent, which inherits from the Apache parent POM, and the versions of that parent in the local repository (30 to 32, checked) declare https://repository.apache.org/snapshots as a snapshots-only repository. It is harmless here (snapshots only, never asked for a release) but it is the kind of silently inherited repository that the repository chapter explains how to block; see Repositories, Mirrors & Repository Managers. (central is listed twice; that is how 3.10.0 reports the default and the effective entry.)

go-offline

dependency:go-offline resolves "everything this project is dependent on (dependencies, plugins, reports)" so that mvn -o can run later. The usage page says it is exactly dependency:resolve dependency:resolve-plugins. To show the effect without touching ~/.m2, a private local repository was used (-Dmaven.repo.local=...). With an empty one, an offline build fails at the first thing it needs, the JUnit BOM:

$ mvn -o -Dmaven.repo.local=/work/mvn-dep-analysis/repo-off verify
[ERROR] Some problems were encountered while processing the POMs:
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.junit:junit-bom:pom:6.1.3 (absent): Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.junit:junit-bom:pom:6.1.3 has not been downloaded from it before. @ line 26, column 19
[ERROR] 'dependencies.dependency.version' for org.junit.jupiter:junit-jupiter:jar is missing. @ line 37, column 17

After mvn -Dmaven.repo.local=... dependency:go-offline (BUILD SUCCESS, 160 JARs and 51 MB downloaded in the fresh repository, including the compiler, resources, surefire, jar, clean, install, deploy, enforcer, dependency and even the site plugin) the build gets further, but it still fails offline, at the tests:

[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[WARNING] The POM for org.apache.maven.surefire:surefire-junit-platform:jar:3.6.0 is missing, no dependency information available
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.6.0:test (default-test) on project tidepool-engine: The following artifacts could not be resolved: org.apache.maven.surefire:surefire-junit-platform:jar:3.6.0 (absent): Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.maven.surefire:surefire-junit-platform:jar:3.6.0 has not been downloaded from it before.

go-offline cannot know which Surefire provider a test run will pick, because the provider is chosen at run time from the test classpath and downloaded then. This is the classic reason why "go-offline" builds still reach out to the network. The remedy is to make the runtime download explicit: declare the provider as a dependency of the plugin, and (JUnit 6 on Surefire 3.6.0) the launcher as a test dependency:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.6.0</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit-platform</artifactId>
      <version>3.6.0</version>
    </dependency>
  </dependencies>
</plugin>

Adding only the provider moved the failure to org.junit.platform:junit-platform-launcher:jar:6.1.3 (absent) (165 JARs). With the provider plugin dependency and org.junit.platform:junit-platform-launcher as a test dependency (its version comes from the junit-bom import), a fresh go-offline (166 JARs, 52 MB) was followed by a fully offline build:

$ mvn -o -Dmaven.repo.local=/work/mvn-dep-analysis/repo-B verify
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.021 s -- in io.tidepool.engine.StationReaderTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] Reactor Summary for tidepool 1.0.0:
[INFO] tidepool ........................................... SUCCESS [  0.001 s]
[INFO] tidepool-model ..................................... SUCCESS [  0.558 s]
[INFO] tidepool-engine .................................... SUCCESS [  0.428 s]
[INFO] tidepool-cli ....................................... SUCCESS [  0.055 s]
[INFO] BUILD SUCCESS

Other limits: excludeReactor (default true) skips modules of the current reactor, so the first multi-module build still has to build them; and the site or release plugins resolve their own tools only when they run. The pragmatic alternative, used by many CI pipelines, is to run one full online mvn verify into a cached local repository and reuse that. Gradle’s --offline works from the dependency cache in the same way (see Dependency Locking, Verification & Caching).

purge-local-repository

dependency:purge-local-repository removes the project’s dependencies from the local repository and, by default, resolves them again (reResolve=true), which is the way to check that a corrupted or half-downloaded artifact is not the problem. It is the one goal here that can destroy data, so the example again uses a private repository. After dependency:resolve into it, the four dependencies of tidepool-report were present (commons-fileupload/1.5, commons-io/2.11.0, commons-csv/1.14.1, commons-codec/1.19.0, plus other Commons IO versions required by the plugins):

$ mvn -Dmaven.repo.local=/work/mvn-dep-analysis/repo-q dependency:purge-local-repository -DreResolve=false -Dverbose
[INFO] --- dependency:3.11.0:purge-local-repository (default-cli) @ tidepool-report ---
[INFO] Deleting 4 transitive dependencies for project tidepool-report from <repo> with artifact version resolution fuzziness
[INFO] Purging artifact: commons-fileupload:commons-fileupload:jar:1.5
[INFO] Deleting: <repo>/commons-fileupload/commons-fileupload/1.5
[INFO] Purging artifact: org.apache.commons:commons-csv:jar:1.14.1
[INFO] Deleting: <repo>/org/apache/commons/commons-csv/1.14.1
[INFO] Purging artifact: commons-io:commons-io:jar:2.11.0
[INFO] Deleting: <repo>/commons-io/commons-io/2.11.0
[INFO] Purging artifact: commons-codec:commons-codec:jar:1.19.0
[INFO] Deleting: <repo>/commons-codec/commons-codec/1.19.0

(<repo> stands for the private repository path.) The version directories of those four artifacts were gone afterwards; the other Commons IO versions (2.17.0, 2.20.0, 2.21.0) that the plugins use stayed. The same goal with -DresolutionFuzziness=file -Dexclude=commons-io:commons-io deleted only the three JAR files of the other artifacts and left the .pom, checksum and _remote.repositories files, and Commons IO untouched.

Parameter User property Meaning

reResolve

reResolve

Default true: download again after deleting. Set false from the command line when you only want to clean.

resolutionFuzziness

resolutionFuzziness

file, version (default), artifactId or groupId: how much to delete around each artifact.

actTransitively

actTransitively

Default true; false for direct dependencies only.

include, includes; exclude, excludes

include, exclude

groupId:artifactId lists; the include forms are since 2.6.

manualInclude, manualIncludes

manualInclude

Since 2.6: delete these given artifacts instead of the project’s; it also works outside a project.

snapshotsOnly

snapshotsOnly

Since 2.4: purge only snapshot artifacts.

verbose

verbose

Print each deletion.

Outside a project, -DmanualInclude=commons-io:commons-io:2.11.0 printed Deleting 1 manual dependency from <repo> and removed the 2.11.0 directory. Never run these goals with -Dmaven.repo.local pointing at your shared ~/.m2 unless you mean it.

Editing the POM from the command line: add and remove

dependency:add and dependency:remove are new in dependency plugin 3.11.0. They edit pom.xml in place while preserving comments, indentation and encoding. Detecting the missing declaration reported above, the fix is:

$ mvn -pl tidepool-cli dependency:add -Dgav=io.tidepool:tidepool-model:1.0.0
[INFO] --- dependency:3.11.0:add (default-cli) @ tidepool-cli ---
[INFO] Added dependency io.tidepool:tidepool-model:1.0.0 to pom.xml
[INFO] BUILD SUCCESS

The change to tidepool-cli/pom.xml was exactly one new <dependency> element after the existing ones, and the following mvn verify with the analyze-only gate passed in all four modules. A second add of the same coordinates fails with:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.11.0:add (default-cli) on project tidepool-cli: Dependency io.tidepool:tidepool-model already exists in pom.xml. Remove it first with dependency:remove, then re-add. -> [Help 1]
Parameter User property Meaning (all since 3.11.0)

gav

gav

groupId:artifactId[:version] or groupId:artifactId[:extension[:classifier]]:version. The version may be omitted when it is managed.

scope, type, classifier, optional

same names

Override the attributes. Scopes are validated against compile, provided, runtime, test, system, import.

managed

managed

Insert into <dependencyManagement> instead of <dependencies> (with -Dscope=import -Dtype=pom, a BOM import).

profile

profile

Target an existing profile by id.

align, useProperty, propertyName

same names

align (default true) follows the project’s conventions: when most dependencies are version-less it puts the version in the parent’s management, and it can create a *.version property.

Adding a managed version in the parent and a version-less test dependency in a child, then removing the managed entry again:

$ mvn -pl . dependency:add -Dmanaged=true -Dgav=org.assertj:assertj-core:3.27.3
[INFO] Added dependency org.assertj:assertj-core:3.27.3 to pom.xml
$ mvn -pl tidepool-engine dependency:add -Dgav=org.assertj:assertj-core -Dscope=test
[INFO] Added dependency org.assertj:assertj-core [scope=test] to pom.xml
$ mvn -pl . dependency:remove -Dmanaged=true -Dgav=org.assertj:assertj-core
[WARNING] The following child modules depend on org.assertj:assertj-core without an explicit version and will break: [tidepool-engine]. Proceeding with removal.
[INFO] Removed dependency org.assertj:assertj-core from pom.xml

The warning is accurate. The next build of the project fails at model-building time with 'dependencies.dependency.version' for org.assertj:assertj-core:jar is missing. @ line 26, column 17, and because add and remove themselves need a loadable project, dependency:remove can no longer repair it: the POM must be edited by hand. Keep the goals for small, deliberate edits. remove matches groupId, artifactId, type (default jar) and classifier, refuses to edit a dependency whose version comes from a property reference in the raw POM, and takes the same managed and profile options.

Enforcing convergence: the enforcer rules

The analysis goals tell you; the enforcer plugin makes the build refuse. Two built-in rules police the shape of the graph:

  • dependencyConvergence requires that every artifact resolves to one version in all paths of the tree. If A needs C 1.0 and B needs C 2.0 the rule fails, even if mediation would pick one of them quietly.

  • requireUpperBoundDeps requires that the version Maven selected is equal to or higher than the highest version requested anywhere in the tree. It targets the dangerous half of the problem: a library running against an older version than it asked for.

Both are bound to the enforcer’s enforce goal. This was added to the tidepool-report POM (the enforcer plugin is pinned to 3.6.3 in pluginManagement):

<build>
  <plugins>
    <plugin>
      <artifactId>maven-enforcer-plugin</artifactId>
      <executions>
        <execution>
          <id>enforce-dependencies</id>
          <goals>
            <goal>enforce</goal>
          </goals>
          <configuration>
            <rules>
              <dependencyConvergence/>
              <requireUpperBoundDeps/>
            </rules>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

mvn validate (the enforce goal’s default phase) fails, with both rules reported and the paths to both versions:

[INFO] --- enforcer:3.6.3:enforce (enforce-dependencies) @ tidepool-report ---
[ERROR] Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence failed with message:
[ERROR] Failed while enforcing releasability.
[ERROR] Dependency convergence error for commons-io:commons-io:jar:2.11.0. Paths to dependency are:
[ERROR] +-io.tidepool:tidepool-report:jar:1.0.0
[ERROR]   +-commons-fileupload:commons-fileupload:jar:1.5:compile
[ERROR]     +-commons-io:commons-io:jar:2.11.0:compile
[ERROR] and
[ERROR] +-io.tidepool:tidepool-report:jar:1.0.0
[ERROR]   +-org.apache.commons:commons-csv:jar:1.14.1:compile
[ERROR]     +-commons-io:commons-io:jar:2.20.0:compile
[ERROR] Rule 1: org.apache.maven.enforcer.rules.dependency.RequireUpperBoundDeps failed with message:
[ERROR] Failed while enforcing RequireUpperBoundDeps. The error(s) are [
[ERROR] Require upper bound dependencies error for commons-io:commons-io:2.11.0. Paths to dependency are:
[ERROR] +-io.tidepool:tidepool-report:1.0.0
[ERROR]   +-commons-fileupload:commons-fileupload:1.5
[ERROR]     +-commons-io:commons-io:2.11.0
[ERROR] and
[ERROR] +-io.tidepool:tidepool-report:1.0.0
[ERROR]   +-org.apache.commons:commons-csv:1.14.1
[ERROR]     +-commons-io:commons-io:2.20.0
[ERROR] ]

(The BUILD FAILURE banner, the Failed to execute goal ...:enforce (enforce-dependencies) line and the standard help lines were left out.) The output was identical on Maven 3.9.16 and 4.0.0-rc-7. The standalone invocation mvn enforcer:enforce -Denforcer.rules=requireUpperBoundDeps works from the command line; the older -Drules still works but prints Parameter 'commandLineRules' (user property 'rules') is deprecated: Use enforcer.rules property instead.

Four ways to make it converge, and what each does

The same failing POM was changed in four ways, each time re-running both rules.

Change dependencyConvergence requireUpperBoundDeps Comment

dependencyManagement pins commons-io:commons-io to 2.20.0

passed

passed

The recommended fix. Management applies to every path, so the tree has one version. In a multi-module build put it in the parent or a BOM (Dependency Management & BOMs).

<exclusions> of commons-io on commons-fileupload

passed

passed

Only Commons CSV’s 2.20.0 remains. Commons FileUpload 1.5 now runs against Commons IO 2.20.0, which it was never built with.

A direct dependency on commons-io:2.20.0

failed

passed

The direct, highest version satisfies the upper bound, but there are still three different paths with two versions (2.20.0 directly, 2.11.0 through FileUpload, 2.20.0 through CSV).

Swap the declaration order (CSV first)

failed

passed

Mediation now picks 2.20.0, so the upper bound is satisfied, but the divergence is still reported. This shows that the declaration-order tie-break was hiding the problem.

The direct-dependency result is the one that surprises people: declaring the version you want does not make the tree converge. Only dependency management or an exclusion removes the competing path. A fifth option, upgrading the stale library, helps only if the newer release has caught up: commons-fileupload 1.6.0 depends on Commons IO 2.19.0, which is still below Commons CSV’s 2.20.0, and both rules still failed.

Tuning the rules

Parameter Meaning

includes

Only enforce for matching artifacts (format groupId[:artifactId][:version][:type][:scope][:classifier] for dependencyConvergence, groupId[:artifactId] for requireUpperBoundDeps). Empty means all.

excludes

Exceptions to the includes. Wildcards are allowed in dependencyConvergence, which also uses the enforcer version-range syntax in the version segment.

excludedScopes

dependencyConvergence: scopes not enforced; if absent, provided and test are excluded. requireUpperBoundDeps: default test and provided.

uniqueVersions

Compare snapshots by their timestamped versions (default false).

With <excludes><exclude>commons-io:commons-io</exclude></excludes> inside each rule, both rules passed on the failing POM: that is the "known and accepted" escape hatch; keep such lists short and commented. The rules also run on a test scope only if you remove test from excludedScopes. Other rules that guard the graph (bannedDependencies, banDuplicatePomDependencyVersions, requireReleaseDeps, requirePluginVersions) are covered on The Enforcer Plugin.

Keeping up: the MojoHaus Versions plugin (third party)

The Versions Maven Plugin, org.codehaus.mojo:versions-maven-plugin, is not an Apache Maven plugin. It belongs to MojoHaus, which took over the former Codehaus Mojo project (the org.codehaus.mojo coordinates were kept). Version 2.23.0 was used. Pin it like any plugin; mvn versions:... resolves the prefix through the default org.codehaus.mojo plugin group, but an unpinned plugin is a moving target. The examples use the full coordinates.

$ mvn org.codehaus.mojo:versions-maven-plugin:2.23.0:display-dependency-updates
[INFO] --- versions:2.23.0:display-dependency-updates (default-cli) @ tidepool-report ---
[INFO] The following dependencies in Dependencies have newer versions:
[INFO]   commons-fileupload:commons-fileupload ................... 1.5 -> 1.6.0

Only Commons FileUpload is listed: Commons CSV 1.14.1 is already the newest, and the transitive Commons IO is not reported because the goal looks at the project’s own declarations (and management); -Dverbose would also list the dependencies without updates. Useful parameters: allowMajorUpdates, allowMinorUpdates, allowIncrementalUpdates (all default true), allowSnapshots, dependencyIncludes/dependencyExcludes, processDependencyManagement, ignoredVersions (regex list, to skip milestones) and showNewestPerSegment (since 2.23.0).

display-plugin-updates does the same for plugins and also checks the minimum Maven version. In the test project it reported that all plugins use the latest versions, then printed the warning Project does not define minimum Maven version required for build and a long list of old plugin versions that "require Maven 2.0" and so on; read only the first lines.

use-latest-releases rewrites the POM:

$ mvn org.codehaus.mojo:versions-maven-plugin:2.23.0:use-latest-releases -DgenerateBackupPoms=false
[INFO] --- versions:2.23.0:use-latest-releases (default-cli) @ tidepool-report ---
[INFO] Updated commons-fileupload:commons-fileupload:jar:1.5 to version 1.6.0 in io.tidepool:tidepool-report:1.0.0

20c20
<       <version>1.5</version>
---
>       <version>1.6.0</version>

By default the plugin leaves a pom.xml.versionsBackup; the run without -DgenerateBackupPoms=false produced a backup byte identical to the original, and versions:revert restored it (Restoring ...pom.xml from ...pom.xml.versionsBackup). In a Git repository the backup file is unnecessary; use the flag. use-latest-releases honours the same allow*Updates switches, plus includes/excludes, scope, processParent (default false) and excludeReactor (default true). Other goals are use-latest-versions, use-next-releases, update-properties, set and commit/revert.

This is the update workflow the convergence rules want: the rewrite takes the stale library to 1.6.0, whose Commons IO is 2.19.0, and, as measured above, the enforcer still fails until management pins 2.20.0. Updating blindly can also take a dependency across a major version; start with -DallowMajorUpdates=false.

Source: versions:revert

When each tool appeared

Feature Since Note

dependency:tree -Dverbose, -Dincludes, -Dexcludes

plugin 2.0-alpha-6

outputType 2.2; json 3.7.0.

dependency:analyze

2.0-alpha-3

analyze-only 2.0; ignore lists 2.10; usedDependencies 2.6; ignoreUnusedRuntime 3.2.0; ignoredNonTestScopedDependencies 3.3.0; excludedClasses 3.7.0.

dependency:analyze-exclusions

3.7.0

Reports unnecessary exclusions.

dependency:list-repositories

2.2

graphRoots on copy/go-offline/build-classpath

3.10.0

dependency:add, dependency:remove

3.11.0

Current release.

dependency:sources

deprecated

Use resolve-sources.

Stricter POM validation (duplicate dependencies and plugins are errors)

Maven 3.10.0

Turns the analyze-duplicate check into a build failure.

Comparable tools in Ant, Ivy and Gradle

None of these rows was run for this page; they are named from the official documentation.

Task Maven Gradle Ant with Ivy

Show the resolved graph

dependency:tree

dependencies and dependencyInsight

ivy:report, and the ivy:resolve log

Fail on conflicting versions

enforcer dependencyConvergence

resolutionStrategy.failOnVersionConflict()

the strict conflict manager

Unused or undeclared dependencies

dependency:analyze

not a core Gradle task

no standard task

Copy dependencies to a directory

dependency:copy-dependencies

a Copy task from a configuration

ivy:retrieve

Newer versions available

MojoHaus versions

version catalogs plus external tools

no standard task

Where the books are outdated

The Maven 2 era books (the issue’s AM2 table) show dependency:analyze and dependency:tree from the plugin’s 2.x line. Today the plugin is 3.11.0 and a chapter written then is missing a lot:

  • dependency:analyze-exclusions (3.7.0), the json tree format (3.7.0), graphRoots (3.10.0) and dependency:add/remove (3.11.0) did not exist.

  • A POM with duplicate dependencies, once a warning, is a build error on Maven 3.10.0.

  • dependency:sources and the silent parameter are deprecated; -q replaces silent.

  • The Codehaus Mojo plugins were renamed to MojoHaus; the Versions plugin lives at mojohaus.org, and the old dependency plugin at Mojo (dependency-maven-plugin) is the same project, renamed and moved to Apache (the plugin FAQ says so).

  • Convergence enforcement was usually described with the old enforcer rule names and -Drules; the rule parameters and the enforcer.rules property above are current.

Summary of pitfalls

  • dependency:tree without -Dverbose hides the loser of every conflict; use -Dverbose before blaming a library.

  • On a tie in depth, the first declaration wins; reordering dependencies can change the classpath silently.

  • dependency:analyze forks test-compile; on a large build prefer analyze-only bound to verify and ignore known false positives instead of turning failOnWarning off.

  • Aggregator artifacts (junit-jupiter, many BOM-like starters) always look unused; the real classes belong to the artifacts below them.

  • A direct declaration of a managed dependency overrides the management; analyze-dep-mgt ignores that unless -Dmdep.analyze.ignore.direct=false.

  • go-offline does not fetch the Surefire provider or the JUnit launcher; pre-declare them, or warm the cache with a real build.

  • purge-local-repository with the default reResolve=true goes online; with a shared ~/.m2 it deletes for every project.

  • dependency:add and remove change only what you ask; they do not check that child modules still build.

  • Direct declaration is not convergence. Use dependencyManagement, a BOM or an exclusion.