Maven Dependency Plugin
last modified November 9, 2026
In this article we work with the Maven Dependency Plugin. We inspect the dependencies of a project, find dependencies that are declared but unused or used but undeclared, edit the POM from the command line, copy dependencies for running an application outside Maven, and prepare a project for offline builds. The examples use Maven 3.10, Java 25, and version 3.11.0 of the plugin.
The Dependency Plugin is not bound to any phase by default. Its goals are run
from the command line with the dependency prefix, as in
mvn dependency:tree, or bound to a phase in the POM. Without a
version in the POM, Maven uses the latest release found in the repository,
so the behaviour of a build can change when a new version comes out; we pin
the version.
The example project
Our project reads users from a JSON file with Jackson 3. It is deliberately imperfect: one dependency is unused, and some of the libraries it uses are not declared.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>users</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.1</version>
</plugin>
</plugins>
</build>
</project>
The project declares Jackson Databind, Commons Lang, and the JUnit Jupiter aggregator for tests. The Dependency and Surefire plugins are pinned to fixed versions.
package com.example;
import com.fasterxml.jackson.annotation.JsonProperty;
public record User(@JsonProperty("first_name") String firstName,
@JsonProperty("last_name") String lastName,
String occupation) {
public String fullName() {
return firstName + " " + lastName;
}
}
The User record maps the snake case names of the JSON file to
its components with the @JsonProperty annotation.
package com.example;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.util.List;
public class App {
public static void main(String[] args) throws IOException {
var mapper = JsonMapper.builder().build();
try (var in = App.class.getResourceAsStream("/users.json")) {
List<User> users = mapper.readValue(in,
mapper.getTypeFactory().constructCollectionType(List.class, User.class));
users.forEach(u -> System.out.printf("%-12s %s%n", u.fullName(), u.occupation()));
} catch (JacksonException e) {
System.err.println("invalid data: " + e.getOriginalMessage());
}
}
}
The program reads the users.json resource into a list of users
and prints them. In Jackson 3, the exceptions are unchecked; we catch
JacksonException to report invalid data.
[
{"first_name": "John", "last_name": "Doe", "occupation": "gardener"},
{"first_name": "Roger", "last_name": "Roe", "occupation": "driver"},
{"first_name": "Lucy", "last_name": "Smith", "occupation": "teacher"}
]
The JSON file contains three users.
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UserTest {
@Test
void fullName() {
var user = new User("John", "Doe", "gardener");
assertEquals("John Doe", user.fullName());
}
}
A small test checks the fullName method.
Displaying the dependency tree
The tree goal displays the resolved dependency graph. Every line
shows the coordinates of an artifact and its scope. Artifacts below a
dependency are its transitive dependencies.
The includes parameter filters the tree. It takes a pattern of
the form groupId:artifactId:type:version, where trailing parts
may be omitted and * is a wildcard. The tree then shows only the
paths that lead to the matching artifacts, which answers the common question
of how a library got into a project. The excludes parameter
works the other way around, and -Dverbose adds the versions that
lost in conflict mediation, as shown in the
exclusions article.
$ mvn dependency:tree [INFO] com.example:users:jar:1.0.0 [INFO] +- tools.jackson.core:jackson-databind:jar:3.2.4:compile [INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.22:compile [INFO] | \- tools.jackson.core:jackson-core:jar:3.2.4:compile [INFO] +- org.apache.commons:commons-lang3:jar:3.21.0: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 ... $ mvn dependency:tree -Dincludes=org.junit.platform [INFO] com.example:users:jar:1.0.0 [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.junit.platform:junit-platform-commons: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 ...
Listing dependencies
The list goal prints the resolved dependencies as a flat list.
For each artifact, it also shows the name of the Java module the JAR
declares or would get as an automatic module.
$ mvn dependency:list [INFO] The following files have been resolved: [INFO] tools.jackson.core:jackson-databind:jar:3.2.4:compile -- module tools.jackson.databind [INFO] com.fasterxml.jackson.core:jackson-annotations:jar:2.22:compile -- module com.fasterxml.jackson.annotation [INFO] tools.jackson.core:jackson-core:jar:3.2.4:compile -- module tools.jackson.core [INFO] org.apache.commons:commons-lang3:jar:3.21.0:compile -- module org.apache.commons.lang3 [INFO] org.junit.jupiter:junit-jupiter:jar:6.1.3:test -- module org.junit.jupiter [INFO] org.junit.jupiter:junit-jupiter-api:jar:6.1.3:test -- module org.junit.jupiter.api [INFO] org.opentest4j:opentest4j:jar:1.3.0:test -- module org.opentest4j [INFO] org.junit.platform:junit-platform-commons:jar:6.1.3:test -- module org.junit.platform.commons [INFO] org.apiguardian:apiguardian-api:jar:1.1.2:test -- module org.apiguardian.api [INFO] org.jspecify:jspecify:jar:1.0.0:test -- module org.jspecify [INFO] org.junit.jupiter:junit-jupiter-params:jar:6.1.3:test -- module org.junit.jupiter.params [INFO] org.junit.jupiter:junit-jupiter-engine:jar:6.1.3:test -- module org.junit.jupiter.engine [INFO] org.junit.platform:junit-platform-engine:jar:6.1.3:test -- module org.junit.platform.engine ...
Analyzing dependencies
The analyze goal compares the declared dependencies with the
classes that the compiled code actually references. It runs the build up to
the test-compile phase first, because it inspects the bytecode
of the main and test classes.
The analysis reports two kinds of problems. Used undeclared dependencies are
artifacts that our code uses directly but gets only transitively. We use the
@JsonProperty annotation from jackson-annotations
and the JacksonException class from jackson-core,
and the test uses the JUnit API. If Jackson Databind or the JUnit aggregator
stopped depending on them, our build would break. Unused declared
dependencies are artifacts that the code does not reference. Commons Lang is
not used at all; the JUnit aggregator only groups other artifacts.
The analysis works on bytecode, so it does not see classes used only through
reflection, service loaders, or configuration files, and it does not see
constants inlined by the compiler. Runtime libraries such as JDBC drivers and
logging backends are typically reported as unused; it is better to give them
the runtime scope or list them in the
ignoredUnusedDeclaredDependencies parameter than to remove them.
$ mvn dependency:analyze [INFO] >>> dependency:3.11.0:analyze (default-cli) > test-compile @ users >>> [INFO] <<< dependency:3.11.0:analyze (default-cli) < test-compile @ users <<< [INFO] --- dependency:3.11.0:analyze (default-cli) @ users --- [WARNING] Used undeclared dependencies found: [WARNING] com.fasterxml.jackson.core:jackson-annotations:jar:2.22:compile [WARNING] tools.jackson.core:jackson-core:jar:3.2.4:compile [WARNING] org.junit.jupiter:junit-jupiter-api:jar:6.1.3:test [WARNING] Unused declared dependencies found: [WARNING] org.apache.commons:commons-lang3:jar:3.21.0:compile [WARNING] org.junit.jupiter:junit-jupiter:jar:6.1.3:test ...
Analysis in the build
We fix the findings: the used libraries are declared directly, Commons Lang is removed, and the JUnit aggregator is replaced with the API artifact. Then we make the analysis part of the build.
<dependencies>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.22</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
The JUnit engine is not needed in the POM. When Surefire finds the JUnit Jupiter API on the test classpath, it adds the matching engine to the test run by itself.
<artifactId>maven-dependency-plugin</artifactId>
<version>3.11.0</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
</configuration>
</execution>
</executions>
</plugin>
The analyze-only goal performs the same analysis as
analyze, but it does not fork a build up to
test-compile; it is meant to be bound to the lifecycle. Its
default phase is verify. With failOnWarning, any
finding fails the build.
The analysis runs after the tests and reports no problems. When nothing is found, the goal prints nothing.
$ mvn verify [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.039 s -- in com.example.UserTest [INFO] --- dependency:3.11.0:analyze-only (analyze) @ users --- [INFO] BUILD SUCCESS
Adding and removing dependencies
Version 3.11.0 added the add and remove goals,
which edit the pom.xml file. The gav parameter
takes the coordinates of the dependency. The scope,
optional, and managed parameters control where and
how the dependency is written; with -Dmanaged, the goals work
with the dependencyManagement section.
We add Commons Lang back. The goal appends a dependency element
to the POM, keeping the formatting of the file. Our code does not use the
library, so the analysis bound in the previous section fails the build. The
remove goal deletes the dependency again; for removal, the
version can be omitted.
$ mvn dependency:add -Dgav=org.apache.commons:commons-lang3:3.21.0 [INFO] Added dependency org.apache.commons:commons-lang3:3.21.0 to pom.xml [INFO] BUILD SUCCESS $ mvn verify [INFO] --- dependency:3.11.0:analyze-only (analyze) @ users --- [ERROR] Unused declared dependencies found: [ERROR] org.apache.commons:commons-lang3:jar:3.21.0:compile [ERROR] BUILD FAILURE ... $ mvn dependency:remove -Dgav=org.apache.commons:commons-lang3 [INFO] Removed dependency org.apache.commons:commons-lang3 from pom.xml [INFO] BUILD SUCCESS
Unnecessary exclusions
Exclusions tend to stay in a POM after the dependency that required them has
been upgraded. The analyze-exclusions goal reports exclusions
that do not remove anything.
<dependencies>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.4</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.15.1</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Both dependencies exclude Commons Logging. Commons Configuration depends on it, while Jackson Databind does not.
The goal reports the exclusion on Jackson Databind with its line number in
the POM. The exclusion on Commons Configuration has an effect and is not
reported. By default, the goal only warns; with
mdep.exclusion.fail set to true, it fails the build.
$ mvn dependency:analyze-exclusions [WARNING] exclusions defines following unnecessary excludes [WARNING] tools.jackson.core:jackson-databind:3.2.4 [WARNING] - commons-logging:commons-logging @ line: 17 [INFO] BUILD SUCCESS $ mvn dependency:analyze-exclusions -Dmdep.exclusion.fail=true [ERROR] exclusions defines following unnecessary excludes [ERROR] tools.jackson.core:jackson-databind:3.2.4 [ERROR] - commons-logging:commons-logging @ line: 17 [ERROR] BUILD FAILURE ...
Building a classpath
The build-classpath goal prints the paths of the dependency JARs
in the local repository, joined with the path separator of the platform. It
is handy for running a program or a tool outside Maven with exactly the
classpath Maven resolved.
We write the classpath to the cp.txt file. The
includeScope parameter selects the dependencies that a scope
needs: runtime means the compile and runtime dependencies,
without the test ones. The tr command prints one entry per
line. The program then runs with plain java.
$ mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt -DincludeScope=runtime $ tr : '\n' < cp.txt /home/user/.m2/repository/tools/jackson/core/jackson-databind/3.2.4/jackson-databind-3.2.4.jar /home/user/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar /home/user/.m2/repository/tools/jackson/core/jackson-core/3.2.4/jackson-core-3.2.4.jar /home/user/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar $ mvn -q compile $ java -cp "target/classes:$(cat cp.txt)" com.example.App John Doe gardener Roger Roe driver Lucy Smith teacher
Copying dependencies
The copy-dependencies goal copies the dependency JARs into a
directory, target/dependency by default. Bound to the
package phase, it produces a thin JAR with a directory of
libraries, a common layout for distributing applications.
<artifactId>maven-dependency-plugin</artifactId>
<version>3.11.0</version>
<executions>
<execution>
<id>copy-libs</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
The execution copies the runtime dependencies into target/lib.
The goal has many filters, such as includeGroupIds,
excludeArtifactIds, and excludeTransitive; the
stripVersion parameter removes versions from file names.
The lib directory contains the three Jackson JARs. The
lib/* wildcard in the classpath is expanded by the
java launcher, so it must be quoted in the shell. The
executable JAR article shows how to add
a Class-Path manifest entry that points to such a directory.
$ mvn -q package $ ls target/lib jackson-annotations-2.22.jar jackson-core-3.2.4.jar jackson-databind-3.2.4.jar $ java -cp "target/users-1.0.0.jar:target/lib/*" com.example.App John Doe gardener Roger Roe driver Lucy Smith teacher
Getting single artifacts
Several goals work with artifacts given by coordinates rather than with the dependencies of the project. They also work outside a project directory.
The get goal downloads an artifact and its transitive
dependencies into the local repository. The copy goal copies
artifacts given by the artifact parameter into a directory; in
the POM, it takes a list of artifactItems. The
list-classes goal lists the classes of an artifact, which is
useful when we look for the JAR that contains a class. The related
unpack goal extracts the contents of an artifact.
$ mvn dependency:get -Dartifact=org.apache.commons:commons-csv:1.14.1 [INFO] Resolving org.apache.commons:commons-csv:jar:1.14.1 with transitive dependencies [INFO] BUILD SUCCESS $ mvn dependency:copy -Dartifact=org.apache.commons:commons-csv:1.14.1 -DoutputDirectory=libs [INFO] BUILD SUCCESS $ ls libs commons-csv-1.14.1.jar $ mvn dependency:list-classes -Dartifact=org.apache.commons:commons-csv:1.14.1 [INFO] org.apache.commons.csv.CSVException [INFO] org.apache.commons.csv.CSVFormat$1 [INFO] org.apache.commons.csv.CSVFormat$Builder [INFO] org.apache.commons.csv.CSVFormat$Predefined [INFO] org.apache.commons.csv.CSVFormat [INFO] org.apache.commons.csv.CSVParser$1 [INFO] org.apache.commons.csv.CSVParser$Builder [INFO] org.apache.commons.csv.CSVParser$CSVRecordIterator [INFO] org.apache.commons.csv.CSVParser$Headers [INFO] org.apache.commons.csv.CSVParser [INFO] org.apache.commons.csv.CSVPrinter [INFO] org.apache.commons.csv.CSVRecord ...
Downloading sources
The resolve-sources goal downloads the source JARs of the
dependencies into the local repository, where IDEs find them. The older
sources goal does the same, but it is deprecated. The
resolve goal with -Dclassifier=javadoc downloads
the Javadoc JARs.
The goal lists the resolved source JARs. It also prints a list of artifacts that have not been resolved, which contains the regular JARs; this is expected, because only the sources were requested.
$ mvn dependency:resolve-sources -DincludeScope=runtime [INFO] The following files have been resolved: [INFO] tools.jackson.core:jackson-databind:jar:sources:3.2.4 [INFO] com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.22 [INFO] tools.jackson.core:jackson-core:jar:sources:3.2.4 [INFO] org.apache.commons:commons-lang3:jar:sources:3.21.0 ...
Preparing for offline builds
The go-offline goal downloads the dependencies of the project,
its plugins, and their dependencies, so that the build can later run with
the -o option, without network access. To see what is
downloaded, we use an empty local repository in the project directory, set
with -Dmaven.repo.local.
The goal succeeds, yet the offline build fails in the test phase. Some
plugins resolve additional artifacts at runtime, depending on the project.
Surefire picks the JUnit Platform provider only when it sees JUnit on the
test classpath, and the provider in turn needs the JUnit Platform launcher;
go-offline cannot know about such artifacts. A more reliable way
to fill the local repository, for instance in a container image or a CI
cache, is to run the real build once with network access, such as
mvn verify.
$ mvn dependency:go-offline -Dmaven.repo.local=repo [INFO] --- dependency:3.11.0:go-offline (default-cli) @ users --- [INFO] BUILD SUCCESS $ mvn -o package -Dmaven.repo.local=repo [INFO] --- surefire:3.6.1:test (default-test) @ users --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [ERROR] BUILD FAILURE [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.6.1:test (default-test) on project users: The following artifacts could not be resolved: org.apache.maven.surefire:surefire-junit-platform:jar:3.6.1 (absent): ...
Summary of goals
| Goal | Description |
|---|---|
tree | displays the dependency graph |
list | lists the resolved dependencies |
analyze | reports unused declared and used undeclared dependencies |
analyze-only | the same analysis, meant for binding to the lifecycle |
analyze-exclusions | reports exclusions that remove nothing |
analyze-dep-mgt | reports mismatches between dependency management and resolved versions |
add, remove | add or remove a dependency in the POM |
build-classpath | prints or writes the classpath of the dependencies |
copy-dependencies | copies the dependency JARs into a directory |
copy, unpack | copy or extract artifacts given by coordinates |
get | downloads an artifact into the local repository |
list-classes | lists the classes of an artifact |
resolve-sources | downloads the source JARs of the dependencies |
go-offline | downloads dependencies and plugins for offline builds |
purge-local-repository | deletes the dependencies of the project from the local repository |
Source
Apache Maven Dependency Plugin
In this article we have used the goals of the Maven Dependency Plugin to inspect, analyze, edit, copy, and download the dependencies of a project.
Author
List all Java tutorials.