Maven Dependency Management

2 minute read

Dependencies are the libraries your project needs. Maven downloads them automatically, resolves their transitive dependencies, and puts them on the classpath. You just declare what you need.


Declaring Dependencies

<dependencies>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.2.1-jre</version>
    </dependency>

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Dependency Scopes

Scope Compile Classpath Test Classpath Runtime Classpath Packaged in JAR? Use Case
compile (default) ✅ ✅ ✅ ✅ Most libraries
provided ✅ ✅ ❌ ❌ Servlet API (server provides it)
runtime ❌ ✅ ✅ ✅ JDBC drivers
test ❌ ✅ ❌ ❌ JUnit, Mockito
system ✅ ✅ ❌ ❌ Local JAR (avoid this)
<!-- compile scope (default — no need to specify) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.1</version>
</dependency>

<!-- provided — container supplies it -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.1.0</version>
    <scope>provided</scope>
</dependency>

<!-- runtime — not needed at compile time -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
    <scope>runtime</scope>
</dependency>

<!-- test — only for testing -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.12.0</version>
    <scope>test</scope>
</dependency>

Transitive Dependencies

When you add spring-boot-starter-web, Maven also downloads its dependencies (and their dependencies):

spring-boot-starter-web
├── spring-boot-starter
│   ├── spring-boot
│   ├── spring-boot-autoconfigure
│   └── spring-core
├── spring-web
├── spring-webmvc
├── spring-boot-starter-tomcat
│   └── tomcat-embed-core
└── jackson-databind
    ├── jackson-core
    └── jackson-annotations

Dependency Tree

mvn dependency:tree

Output:

[INFO] com.myapp:my-project:jar:1.0.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.3.0:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:3.3.0:compile
[INFO] |  +- org.springframework:spring-web:jar:6.1.8:compile
[INFO] |  +- org.springframework:spring-webmvc:jar:6.1.8:compile
[INFO] |  \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.1:compile
[INFO] +- org.junit.jupiter:junit-jupiter:jar:5.10.3:test

Filter for a specific dependency:

mvn dependency:tree -Dincludes=com.fasterxml.jackson.core

Excluding Transitive Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.0</version>
    <exclusions>
        <!-- Exclude Tomcat, use Jetty instead -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- Replace with Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <version>3.3.0</version>
</dependency>

BOM — Bill of Materials

A BOM manages versions centrally. You declare dependencies without versions:

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

<dependencies>
    <!-- No version needed — BOM provides it -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

Maven Central

The default repository — where Maven downloads dependencies from:

Search: search.maven.org

<!-- This is implicit (from Super POM) — no need to declare -->
<repositories>
    <repository>
        <id>central</id>
        <url>https://repo.maven.apache.org/maven2</url>
    </repository>
</repositories>

Private Repositories (Nexus/Artifactory)

<repositories>
    <repository>
        <id>company-repo</id>
        <url>https://nexus.mycompany.com/repository/maven-releases/</url>
    </repository>
</repositories>

Credentials go in ~/.m2/settings.xml (never in pom.xml):

<settings>
    <servers>
        <server>
            <id>company-repo</id>
            <username>deploy-user</username>
            <password>secret</password>
        </server>
    </servers>
</settings>

SNAPSHOT Versions

<version>1.0.0-SNAPSHOT</version>
Release SNAPSHOT
Immutable Changes with every build
Cached by Maven Re-downloaded periodically
Production-ready Development/in-progress
1.0.0 1.0.0-SNAPSHOT
# Force re-download SNAPSHOTs
mvn clean package -U

Local Repository

Maven caches all downloaded dependencies in:

~/.m2/repository/
├── com/
│   └── google/
│       └── guava/
│           └── guava/
│               └── 33.2.1-jre/
│                   ├── guava-33.2.1-jre.jar
│                   └── guava-33.2.1-jre.pom
# Clear local cache (re-downloads everything)
rm -rf ~/.m2/repository

Common Mistakes

Mistake Fix
Missing <scope>test</scope> on JUnit Test dependencies in production JAR
Version conflicts Use dependency:tree to find conflicts, exclude one
Hardcoded versions everywhere Use BOM or <dependencyManagement>
Putting credentials in pom.xml Use ~/.m2/settings.xml
Not running mvn -U for SNAPSHOTs Stale cached versions

Key Takeaways

  1. Scopes control where dependencies are available — compile, test, provided, runtime.
  2. Maven resolves transitive dependencies automatically — use dependency:tree to inspect.
  3. BOM (<dependencyManagement>) centralizes version management.
  4. Dependencies are cached in ~/.m2/repository.
  5. Use SNAPSHOT for in-progress development, release versions for production.

What’s Next?

Next: Maven Plugins — compiler, surefire, shade, Spring Boot, and how to configure them.

Updated: