Dependency Management

7 min

Table of contents


1 — What is a dependency?

A dependency is an external library your project needs in order to compile or run. Instead of rewriting code (JSON parsing, database access, tests…), you reuse proven libraries.

Maven manages these dependencies automatically: you declare them in the pom.xml, and Maven downloads them and puts them on the classpath.

Without MavenWith Maven
Download each .jar by handDeclare 3 lines in pom.xml
Manage the classpath manuallyMaven builds it automatically
Find compatible librariesAutomatic transitive resolution

A dependency is also identified by its GAV coordinates (groupId:artifactId:version), exactly like your own project. That is how Maven knows what to download.

↑ Back to top


2 — Declare a dependency

Dependencies are declared in the <dependencies> block of the pom.xml. Each dependency is a <dependency> tag with its GAV coordinates.

xml
<dependencies>

    <!-- Google's Gson library for working with JSON -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>

</dependencies>

To find a library's correct coordinates, you consult Maven Central (search.maven.org), which provides the XML block ready to copy.

bash
# After adding a dependency, trigger the download
mvn compile

# Or download explicitly without compiling
mvn dependency:resolve
ElementRole
<groupId>The organization that publishes the library
<artifactId>The library name
<version>The desired version

Maven caches libraries in ~/.m2/repository. A library that is already downloaded is never downloaded again: that is why later builds are fast.

🔧 Mini-exercise — Declare the Gson dependency (com.google.code.gson:gson:2.10.1) in a <dependency> block.

✅ See a solution
xml
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

↑ Back to top


3 — Scopes

A dependency's scope indicates when and where it is available: at compile time, at test time, at runtime, or provided by the environment. You specify it with <scope>.

xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>   <!-- available only for tests -->
</dependency>
ScopeAvailable at compileAvailable at testAvailable at runtimeIncluded in the artifact
compile (default)
test
provided❌ (provided by the server)
runtime

Concrete examples:

LibraryTypical scopeWhy
JUnittestOnly useful for running tests
Servlet APIprovidedThe server (Tomcat) already provides it
JDBC driverruntimeRequired at runtime, not at compile time
GsoncompileUsed everywhere in the code

Putting JUnit in compile instead of test is a common mistake: it unnecessarily ships the test library in your final deliverable. Always choose the most restrictive scope that still fits.

🔧 Mini-exercise — Declare a JUnit Jupiter (5.10.2) dependency with test scope in the pom.xml.

✅ See a solution
xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

↑ Back to top


4 — Repositories

Maven downloads dependencies from repositories. There are three levels:

Repository typeDescription
Local repositoryCache on your machine: ~/.m2/repository
Maven CentralThe worldwide public repository, default source
Private repositoryCompany server (Nexus, Artifactory) for internal libraries

To add a private repository, you declare it in the pom.xml:

xml
<repositories>
    <repository>
        <id>nexus-entreprise</id>
        <url>https://nexus.monentreprise.com/repository/maven-public/</url>
    </repository>
</repositories>
bash
# Force dependency updates from the repositories
mvn clean install -U

# Clear a library's cache to force a re-download
# (manually delete the corresponding folder in ~/.m2)
CaseRepository used
Public open-source libraryMaven Central
In-house company libraryPrivate repository (Nexus/Artifactory)
Library already downloadedLocal cache ~/.m2

Companies often use a private repository as a mirror of Maven Central: that speeds up downloads and lets them control which libraries are allowed.

↑ Back to top


5 — Transitive resolution

A dependency can itself depend on other libraries: those are transitive dependencies. Maven downloads them automatically — you do not have to list them.

You declare one dependency, Maven resolves dozens:

xml
<!-- A single declaration... -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.5</version>
</dependency>
<!-- ...automatically brings in spring-web, jackson, tomcat, etc. -->

To visualize the complete tree:

bash
# Displays the dependency tree (direct + transitive)
mvn dependency:tree

Typical output:

com.exemple:mon-app:jar:1.0.0
+- org.springframework.boot:spring-boot-starter-web:jar:3.2.5:compile
|  +- org.springframework:spring-web:jar:6.1.6:compile
|  +- com.fasterxml.jackson.core:jackson-databind:jar:2.15.4:compile
|  \- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.20:compile

When two paths bring two different versions of the same library, Maven applies the “nearest in the tree” rule (nearest wins): the version closest to your project wins.

Transitive resolution is one of Maven's greatest strengths: you declare high-level intentions, and Maven assembles the whole dependency puzzle for you.

🔧 Mini-exercise — Write the command that displays the complete dependency tree (direct and transitive).

✅ See a solution
bash
mvn dependency:tree

↑ Back to top


6 — Diagnose and exclude

Sometimes a transitive dependency causes trouble (version conflict, unwanted library). Maven provides tools to diagnose and fix that.

To exclude an unwanted transitive dependency:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Useful diagnostic commands:

CommandRole
mvn dependency:treeDisplays the complete dependency tree
mvn dependency:analyzeDetects unused or missing dependencies
mvn dependency:resolveDownloads all dependencies
bash
# Spot declared-but-unused dependencies,
# and used-but-undeclared ones
mvn dependency:analyze

Before excluding anything, run mvn dependency:tree to understand where the problematic dependency comes from. You never exclude blindly.

🔧 Mini-exercise — Write the command that spots declared-but-unused dependencies (and the reverse).

✅ See a solution
bash
mvn dependency:analyze

↑ Back to top


7 — Quiz — Dependency management

Question 1: Where does Maven cache downloaded dependencies?

a) In target/

b) In ~/.m2/repository

c) In src/main/resources

d) On the web server

See the solution

Answer: b) — The local repository ~/.m2/repository acts as a cache: a library that is already downloaded is not downloaded again.


Question 2: Which scope fits JUnit, a library used only for tests?

a) compile

b) runtime

c) test

d) provided

See the solution

Answer: c) — The test scope makes the dependency available to tests only and excludes it from the final deliverable.


Question 3: What are transitive dependencies?

a) Dependencies you must list manually

b) The dependencies of your dependencies, resolved automatically by Maven

c) Temporary dependencies

d) Test dependencies

See the solution

Answer: b) — A declared dependency brings its own dependencies; Maven downloads them automatically.


Question 4: Which command displays the complete dependency tree?

a) mvn list

b) mvn dependency:tree

c) mvn show-deps

d) mvn package

See the solution

Answer: b)mvn dependency:tree shows direct and transitive dependencies, useful for diagnosing conflicts.


Question 5: Which scope should you choose for the Servlet API, provided by the Tomcat server?

a) compile

b) test

c) provided

d) runtime

See the solution

Answer: c)provided: the library is needed to compile, but the runtime environment (the server) already provides it.

↑ Back to top


8 — Practice — Add dependencies

Instructions

In an existing project, add two dependencies: Gson (com.google.code.gson:gson:2.10.1) with compile scope to work with JSON, and JUnit Jupiter (org.junit.jupiter:junit-jupiter:5.10.2) with test scope. Then inspect the dependency tree.


Suggested correction — Expected <dependencies> block

xml
<dependencies>

    <!-- Gson: used everywhere in the code (compile scope by default) -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>

    <!-- JUnit: tests only -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>

</dependencies>

Verification:

bash
# Download and display the dependency tree
mvn dependency:tree

Expected result:

com.exemple:mon-app:jar:1.0.0-SNAPSHOT
+- com.google.code.gson:gson:jar:2.10.1:compile
\- org.junit.jupiter:junit-jupiter:jar:5.10.2:test
   +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test
   +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test
   \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test

Notice that junit-jupiter automatically brings in -api, -params, and -engine: those are its transitive dependencies. You declared only one line; Maven resolved the rest.

↑ Back to top


9 — Summary

Key takeaways

  1. A dependency is an external library, identified by its GAV coordinates.
  2. You declare it in <dependencies>; Maven downloads it and caches it in ~/.m2.
  3. Scopes (compile, test, provided, runtime) control when the dependency is available.
  4. Repositories: local (~/.m2), Maven Central (public), private (Nexus/Artifactory).
  5. Transitive resolution automatically brings in the dependencies of your dependencies; mvn dependency:tree lets you inspect it.

What's next

Lesson 04 — Lifecycle and phases: understand the phases (compile, test, package, install, deploy) that orchestrate the build.

↑ Back to top


All rights reserved. Any reproduction, distribution, use or adaptation of this course, in whole or in part, is strictly prohibited without the prior written authorization of Dr. Haythem REHOUMA.

Course created by Dr. Haythem REHOUMA — Development and Deployment of Data Solutions