ZetCode

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.

pom.xml
<?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.

src/main/java/com/example/User.java
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.

src/main/java/com/example/App.java
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.

src/main/resources/users.json
[
  {"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.

src/test/java/com/example/UserTest.java
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.

pom.xml (dependencies)
<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.

pom.xml (Dependency Plugin)
    <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.

pom.xml (dependencies)
<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.

pom.xml (Dependency Plugin)
    <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
treedisplays the dependency graph
listlists the resolved dependencies
analyzereports unused declared and used undeclared dependencies
analyze-onlythe same analysis, meant for binding to the lifecycle
analyze-exclusionsreports exclusions that remove nothing
analyze-dep-mgtreports mismatches between dependency management and resolved versions
add, removeadd or remove a dependency in the POM
build-classpathprints or writes the classpath of the dependencies
copy-dependenciescopies the dependency JARs into a directory
copy, unpackcopy or extract artifacts given by coordinates
getdownloads an artifact into the local repository
list-classeslists the classes of an artifact
resolve-sourcesdownloads the source JARs of the dependencies
go-offlinedownloads dependencies and plugins for offline builds
purge-local-repositorydeletes 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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.