skip to content

Transitive Resolution & Mediation

How Maven flattens the transitive graph and picks a version by nearest-wins, and how exclusions and <optional> keep unwanted jars off the classpath. Asked because nearest-wins surprises everyone who expects the highest version to win.

on this pageshow

questions

5

What are transitive dependencies in Maven, and where do they come from?

level: juniorimportance: must knowfreq 75%

answer

  1. dependency of a dependency
  2. transitive closure / graph
  3. each artifact ships its own POM
  4. mvn dependency:tree
  5. convenience vs hidden conflicts

basics

~10 s

Transitive dependencies are the dependencies of your dependencies. If you declare library A and A needs B, Maven automatically downloads B too, so you don't have to list it yourself.

solid answer

~40 s

Maven builds a full dependency graph: you declare direct dependencies in your pom.xml, and each of those declares its own dependencies in its published POM, recursively. Maven pulls in that whole transitive closure automatically so you only list what you directly use. Each artifact publishes a POM to the repository describing its dependencies, which is how Maven knows the graph. Transitivity is filtered by scope (e.g. test and provided don't propagate the same way) and can be controlled with <exclusions> to prune unwanted branches and <optional>true</optional> to stop propagation. You can inspect the resolved graph with `mvn dependency:tree`. The benefit is convenience; the risk is pulling in unexpected or conflicting versions, which is why mediation and exclusions exist.

code

bash · 5 lines
bash
mvn dependency:tree
# [INFO] com.example:app:jar:1.0
# [INFO] +- org.springframework:spring-web:jar:6.1.0:compile
# [INFO] |  +- org.springframework:spring-core:jar:6.1.0:compile   (transitive)
# [INFO] |  \- org.springframework:spring-beans:jar:6.1.0:compile  (transitive)

go deeper

for a junior

Know that a dependency's dependencies come in automatically and that mvn dependency:tree shows them.

for a middle

Understand the transitive closure is read from each artifact's POM and filtered by scope.

for a senior

Reason about the trade-off: convenience vs uncontrolled graph growth and conflict risk; use dependency:tree routinely.

for a principal

Set org-wide policy: lock the graph via dependencyManagement/BOMs, enforce with enforcer rules, audit transitive bloat and CVEs.

## What "transitive" means A **direct dependency** is one you write yourself in your project's `pom.xml` (the Project Object Model — Maven's build configuration file). A **transitive dependency** is a dependency of a dependency: something you never named, but that gets pulled in because something you DID name needs it. Example: you depend on `spring-web`. `spring-web` itself depends on `spring-core` and `spring-beans`. You never wrote those, but Maven adds them to your classpath automatically. The complete recursively-collected set is called the **transitive closure** or **dependency graph**. ## How Maven knows the graph Every artifact published to a Maven repository ships with its own POM file alongside the JAR. That POM lists the artifact's own `<dependencies>`. Maven reads it, then reads the POMs of THOSE dependencies, and so on, walking the tree until it has the full closure. ## Why it exists - You only declare what you actually use; the build figures out the rest. - Upgrading one library can automatically bring compatible versions of its requirements. ## The cost - You may get JARs you never asked for. - Two different paths may demand different versions of the same artifact (a **version conflict**), which Maven resolves via **mediation** (nearest-wins). - Unwanted branches must be cut with `<exclusions>`. ## Inspecting it ```bash mvn dependency:tree ``` This prints the whole tree, marking direct vs transitive and showing where versions were omitted for conflict. Concepts to know: scope (controls propagation), `<exclusions>` (prune a branch), `<optional>true</optional>` (don't propagate to consumers).

  • How does Maven physically know what a third-party library depends on?
    Each published artifact has a companion POM file in the repository listing its own dependencies; Maven downloads and reads it to recurse the graph.
  • Does every scope propagate transitively the same way?
    No. compile and runtime propagate; test and provided do not propagate to downstream consumers, so a library's test dependencies never land on your classpath.

Like inviting one friend to dinner who brings their roommates: you only invited one person, but a whole chain shows up because each guest brings their own required companions.

saying these in an interview costs you the question

  • Saying you must manually declare every JAR you need — defeats the purpose of transitivity.
  • Claiming Maven scans the JAR bytecode to discover dependencies (it reads the published POM, not the bytecode).

context

open as a page

How do you stop a specific transitive dependency from being pulled in, and when would you do that?

level: middleimportance: must knowfreq 80%

basics

~20 s

Add an <exclusions> block inside the direct dependency that drags it in, naming the unwanted groupId and artifactId. That prunes it (and its sub-tree) from the graph. Use it for clashing logging libs or vulnerable transitives.

open as a page

When two paths in the dependency graph demand different versions of the same artifact, how does Maven decide which version to use?

level: middleimportance: must knowfreq 85%

basics

~10 s

Maven uses nearest-wins: the version closest to your project in the dependency tree (fewest hops) is chosen. If two are at the same depth, the one declared first in the POM wins.

open as a page

A NoSuchMethodError appears at runtime that you suspect is a dependency version conflict. Walk me through how you'd diagnose and fix it.

level: seniorimportance: should knowfreq 65%

basics

~10 s

Run mvn dependency:tree (with -Dverbose) to find which versions of the artifact are in the graph and which one mediation chose. Then force the correct version via dependencyManagement or a direct declaration, and re-verify.

open as a page

What does <optional>true</optional> do on a dependency, and how does it differ from an exclusion?

level: seniorimportance: should knowfreq 55%

basics

~10 s

<optional>true</optional> marks a dependency you use internally but that should NOT propagate to projects that depend on you. It still applies to your own build; it just stops Maven passing it transitively to consumers.

open as a page