Dependency Management

Dependencies are the reason most people pick up Maven in the first place. Instead of hunting down JAR files on the internet, you declare what you need and Maven fetches it — along with everything that library needs in turn. This chapter shows you how to add dependencies, control their scope, and resolve conflicts when multiple versions of the same library show up.

Prerequisites

  • A working Maven project with a pom.xml
  • Internet connection to reach Maven Central

Adding Your First Dependency

Let us walk through a concrete example. Suppose you want to parse a JSON string and read a field value. You will use Jackson, the most popular JSON library in the Java ecosystem.

Finding the Coordinates

Every dependency is identified by its GAV coordinates. For Jackson, the core databind module is:

ElementValue
groupIdcom.fasterxml.jackson.core
artifactIdjackson-databind
version2.17.0

You can find these coordinates on Maven Central or by searching for the library name plus "maven dependency."

Declaring the Dependency

Open your pom.xml and add a <dependencies> block inside <project>:

xml
<dependencies>
    <!-- Jackson for JSON parsing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version>
    </dependency>
</dependencies>

Save the file. If you are using IntelliJ IDEA, click the Maven reload icon that appears in the top-right corner of the editor, or press the reload shortcut.

Maven downloads jackson-databind and its transitive dependencies (in this case, jackson-core and jackson-annotations) into ~/.m2/repository.

Writing the Code

Create a new Java class in src/main/java/com/example/JsonDemo.java:

java
package com.example;
 
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class JsonDemo {
 
    public static void main(String[] args) throws Exception {
        // Create a reusable JSON parser instance
        ObjectMapper mapper = new ObjectMapper();
 
        // A sample JSON string representing a user
        String json = "{\"name\":\"Alice\",\"age\":30,\"city\":\"Seattle\"}";
 
        // Parse the string into a tree model
        JsonNode root = mapper.readTree(json);
 
        // Extract individual field values
        String name = root.get("name").asText();
        int age = root.get("age").asInt();
        String city = root.get("city").asText();
 
        // Print the extracted values
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("City: " + city);
    }
}

Compile and run:

bash
# Compile the project (Maven downloads dependencies automatically)
mvn compile
 
# Run the main class
mvn exec:java -Dexec.mainClass="com.example.JsonDemo"

You should see:

text
Name: Alice
Age: 30
City: Seattle

Tip

Transitive Dependencies at Work

Notice that you only declared jackson-databind, yet the build succeeded. That is because jackson-databind depends on jackson-core and jackson-annotations, and Maven pulled those in automatically. You did not have to hunt them down yourself.

Dependency Scopes

Not every dependency belongs on the classpath at all times. Maven provides six scopes that control when a dependency is available.

The Six Scopes

ScopeAvailable DuringTypical Use
compileCompile, test, and runtimeDefault. Standard libraries your code needs.
providedCompile and test onlyLibraries the runtime environment already supplies, such as servlet-api in a Tomcat container.
runtimeTest and runtime onlyLibraries required to run but not to compile, such as JDBC drivers.
testTest compilation and execution onlyTesting frameworks like JUnit.
systemCompile and test, like providedDeprecated. Points to a local JAR file via <systemPath>.
importPOM inheritance onlyImports a BOM (Bill of Materials) into <dependencyManagement>.

Example: Mixing Scopes

xml
<dependencies>
    <!-- Available everywhere (default scope is compile) -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version>
    </dependency>
 
    <!-- Only needed for tests -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
 
    <!-- Provided by the container at runtime -->
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>6.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Warning

Do Not Use system Scope

The system scope was a workaround for local JAR files before repositories were common. It breaks portability because the <systemPath> is absolute and machine-specific. Use a repository or install the JAR into your local repository with mvn install:install-file instead.

Dependency Conflicts and Resolution

How Conflicts Arise

Imagine your project directly depends on Library A version 1.0 and Library B version 2.0. Unbeknownst to you, Library B internally depends on Library A version 1.5. Now two versions of Library A are on the classpath. Which one wins?

Maven resolves this automatically using two rules:

  1. Nearest definition — the version closest to your project in the dependency tree wins.
  2. First declaration — if two versions are at the same depth, the one declared first in your pom.xml wins.

Visualizing the Tree

Run this command to see the full dependency hierarchy:

bash
# Print the dependency tree
mvn dependency:tree

For the Jackson example, the output looks something like this:

text
com.example:demo:jar:1.0-SNAPSHOT
\- com.fasterxml.jackson.core:jackson-databind:jar:2.17.0:compile
   +- com.fasterxml.jackson.core:jackson-annotations:jar:2.17.0:compile
   \- com.fasterxml.jackson.core:jackson-core:jar:2.17.0:compile

Notice the indentation. Each level shows a transitive dependency. If a version conflict existed, Maven would mark the losing version with (omitted for conflict with X.Y.Z).

Excluding Transitive Dependencies

Sometimes you need to block a transitive dependency entirely. Perhaps it has a known security vulnerability, or you want to substitute a different implementation.

Exclude jackson-annotations from the jackson-databind dependency like this:

xml
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Tip

When to Exclude

Use exclusions sparingly. They are appropriate when a transitive dependency is unsafe, unnecessary, or replaced by a direct dependency you declare yourself. Overusing them makes your build fragile and hard to maintain.

Centralizing Versions with dependencyManagement

In a large project — especially a multi-module one — you do not want version numbers scattered across dozens of pom.xml files. The <dependencyManagement> block lets you declare versions in one place without actually adding the dependency to the classpath.

xml
<dependencyManagement>
    <dependencies>
        <!-- Define the version here -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <!-- Use the dependency without a version -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <!-- No version tag needed; inherited from dependencyManagement -->
    </dependency>
</dependencies>

This pattern is the foundation of BOMs (Bills of Materials). Frameworks like Spring Boot publish a BOM that pins compatible versions for dozens of libraries. You import the BOM once, then add dependencies without worrying about version mismatches.

FAQ

How do I find the latest version of a library?

Search Maven Central or mvnrepository.com. Both show version history, release dates, and usage statistics. Pick a stable release (not a beta or RC) unless you need bleeding-edge features.

Why does Maven download the same dependency every time I build?

It should not. Maven caches artifacts in ~/.m2/repository. The only exceptions are snapshot versions (-SNAPSHOT), which Maven refreshes periodically by design. If you see repeated downloads for release versions, check your network settings or repository configuration.

Can I use a dependency that is not published to Maven Central?

Yes. You can install it manually into your local repository:

bash
# Install a local JAR into the Maven cache
mvn install:install-file \
  -Dfile=my-library.jar \
  -DgroupId=com.mycompany \
  -DartifactId=my-library \
  -Dversion=1.0.0 \
  -Dpackaging=jar

For team projects, a better solution is a private repository server such as Nexus or Artifactory.

What is a BOM and how do I import one?

A BOM (Bill of Materials) is a special POM that lists dependency versions without declaring the dependencies themselves. Import it like this:

xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

After importing, any Spring Boot dependency you add can omit the version tag because the BOM provides it.

What happens if two dependencies require incompatible versions?

Maven picks one based on the nearest-definition rule. If the chosen version breaks the other library, you have a few options: upgrade the library that depends on the older version, downgrade the direct dependency, or use an exclusion and declare the working version explicitly. There is no magic — only careful version management.