Mastering the Mvn Dependency Tree for Secure Software

When you're working with Maven, the mvn dependency:tree command is your go-to for getting a complete, hierarchical picture of every library in your project. It doesn’t just show you the dependencies you've explicitly declared (direct ones), but also all the other libraries those dependencies pull in (transitive ones). Think of it as a detailed map…

When you're working with Maven, the mvn dependency:tree command is your go-to for getting a complete, hierarchical picture of every library in your project. It doesn’t just show you the dependencies you've explicitly declared (direct ones), but also all the other libraries those dependencies pull in (transitive ones). Think of it as a detailed map of your entire software supply chain.

To see it in action, simply navigate to your project's root directory (where the pom.xml file is) and run:

mvn dependency:tree

Untangling Your Project's Web Of Dependencies

Any modern software project is really built on the shoulders of giants—third-party libraries that save us from reinventing the wheel. But this convenience creates a complex web of dependencies that can easily hide security vulnerabilities and tricky licensing issues. The mvn dependency:tree command is your first and best tool for shining a light into this web, giving you an immediate, clear picture of every single component in your build.

A detailed dependency graph shows a central project connected to numerous software libraries and modules.

Just running this one command can uncover dozens of hidden libraries you probably didn’t even know were part of your application. Gaining this visibility is the essential first step for managing risk effectively.

Understanding Direct and Transitive Dependencies

The output you get from mvn dependency:tree neatly organises everything into a hierarchy. This structure is incredibly useful because it helps you distinguish between two critical types of dependencies:

  • Direct Dependencies: These are the libraries you've explicitly added to your pom.xml file. For example, if you add spring-boot-starter-web to your POM, that's a direct dependency.
  • Transitive Dependencies: These are the libraries your direct dependencies need to work. That same spring-boot-starter-web artifact transitively pulls in other libraries like spring-web, spring-webmvc, and tomcat-embed-core. You didn't ask for them directly, but they're coming along for the ride.

It’s not uncommon for a typical Java web app to have only 10–15 direct dependencies in its POM, but the full dependency tree can easily reveal over 100 transitive dependencies. Every single one of them represents a potential attack surface or a licensing headache you need to be aware of. If you're curious about how different build tools manage these relationships, it’s worth checking out comparisons of Maven vs Gradle.

Interpreting the Hierarchical Output

When you run the command, the output is formatted as a tree. Your project sits at the top, and each indented line below it represents another dependency in the hierarchy.

A typical output might look like this:

[INFO] com.example:my-app:jar:1.0-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.5.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.5.4:compile
[INFO] |  |  - org.yaml:snakeyaml:jar:1.28:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.5.4:compile
[INFO] |  |  - com.fasterxml.jackson.core:jackson-databind:jar:2.12.4:compile
[INFO] |  - org.springframework.boot:spring-boot-starter-tomcat:jar:2.5.4:compile
[INFO] |     - org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.52:compile
[INFO] - junit:junit:jar:4.13.2:test

The dependency tree isn't just a flat list; it's a map of relationships. It tells you exactly why a particular library is included in your build, tracing it all the way back to a specific top-level dependency you intentionally added.

This hierarchical view is fundamental for diagnosing all sorts of problems. It lays the groundwork for deeper analysis and risk mitigation, which is becoming non-negotiable for anyone building products for the EU market under regulations like the Cyber Resilience Act (CRA).

While a tool like mvn dependency:tree is crucial for visibility, it’s just as important to think about how you structure your code to manage dependencies well from the start. For a deeper architectural perspective, mastering the Dependency Inversion Principle can help you design systems that are more flexible, maintainable, and less tangled from day one.

Advanced Analysis With Mvn Dependency Tree Flags

The default mvn dependency:tree output gives you a great starting point, but for any project with real-world complexity, you'll need more control. Mastering its various flags is what separates a quick glance from a deep, forensic analysis. These options let you cut through the noise, focus on specific artifacts, and get the exact information needed to solve problems efficiently.

Think of the default tree as a wide-angle map of a city. To find a specific address, you need to zoom in. The command-line flags are your zoom controls.

Getting More Detail with Dverbose

One of the most powerful flags you'll reach for is -Dverbose. Running mvn dependency:tree -Dverbose enriches the output in a huge way. Instead of just showing the clean, resolved tree, it reveals the complete dependency trail for every single artifact, including versions that were considered but ultimately kicked out by Maven's resolution process.

This is incredibly useful when you’re trying to answer that classic question, "Which dependency brought in this specific transitive library?" The verbose output shows you the full path from a direct dependency down to the one in question, leaving no room for guesswork. It also highlights any version conflicts and shows which version won, which is critical for debugging.

Let's see a practical example. Imagine two of your dependencies request different versions of the guava library. The verbose output would look something like this:

mvn dependency:tree -Dverbose
[INFO] com.example:my-app:jar:1.0-SNAPSHOT
[INFO] +- com.example:alpha-library:jar:1.0:compile
[INFO] |  - (com.google.guava:guava:jar:28.0-jre:compile - omitted for conflict with 29.0-jre)
[INFO] - com.example:beta-library:jar:1.0:compile
[INFO]    - com.google.guava:guava:jar:29.0-jre:compile

The ( ... - omitted for conflict with ...) line only appears with -Dverbose, giving you a clear explanation of the conflict.

Filtering the Tree with Includes and Excludes

For large projects, the full dependency tree can be overwhelming, often spanning thousands of lines. This is where filtering becomes absolutely essential. The -Dincludes and -Dexcludes flags allow you to narrow the output to only what you care about.

You can filter by groupId:artifactId:type:version, and wildcards (*) are fully supported, making it easy to focus on an entire group or a specific library.

  • -Dincludes: Only show dependencies that match the pattern.
  • -Dexcludes: Hide dependencies that match the pattern.

Imagine you're investigating a vulnerability in an older version of the log4j library. Instead of manually searching through a massive text file, you can isolate it with a simple command.

# Focus only on log4j dependencies
mvn dependency:tree -Dincludes=log4j:log4j

This command transforms a wall of text into a concise, actionable report showing exactly where log4j appears in your project and which dependencies introduced it. You might also be interested in how this integrates with broader security tooling; you can check out our guide on OWASP Dependency-Check to see how identifying dependencies is the first step in vulnerability scanning.

To make these flags easier to remember, here's a quick reference table I find myself coming back to all the time.

Essential 'mvn dependency:tree' Flags and Their Uses

Flag Purpose Example Use Case
-Dverbose Shows the complete dependency graph, including omitted and conflicting versions. Diagnosing why a specific version of a library was chosen over another.
-Dincludes Narrows the tree to only show dependencies matching a specific pattern. Finding every instance of *:commons-logging in a large project.
-Dexcludes Hides dependencies that match a pattern, cleaning up the output. Removing all test-scoped dependencies like *:junit from the tree.

Mastering just these three flags will solve the vast majority of dependency investigation tasks you'll encounter.

In my experience, combining -Dverbose with -Dincludes is the fastest way to untangle complex transitive dependency problems. It gives you the full story but only for the specific actors you're investigating.

Let's walk through a real-world scenario. A legacy system is misbehaving, and you suspect a conflict caused by multiple versions of a commons-collections library. The full tree is just too big to analyse by hand.

Here's how your filtering process would look:

  1. Initial Scan: Run mvn dependency:tree -Dincludes=*:commons-collections. This immediately shows you all instances of the library, cutting out everything else.
  2. Identify the Conflict: The output reveals two versions are being pulled in: 3.2.1 and 4.1. The tree clearly shows that 3.2.1 is being omitted in favour of the newer one.
  3. Trace the Source: Now, you run mvn dependency:tree -Dverbose -Dincludes=*:commons-collections. The verbose output will show the exact dependency path that brought in the older, omitted version, pointing you directly to the root cause.

With just two commands, you've diagnosed an issue that could have taken hours of manual inspection. This level of precise control is what makes the mvn dependency:tree command an indispensable tool for maintaining clean, secure, and stable builds.

Diagnosing and Resolving Dependency Conflicts

Let's be honest, dependency conflicts are one of the biggest headaches in Maven projects. You add a new library, run your build, and suddenly everything breaks because of a "convergence error." This happens when different dependencies in your project demand conflicting versions of the same transitive library.

The mvn dependency:tree command is your best friend here. It’s the tool I reach for first to figure out exactly why these conflicts are happening and, crucially, which version Maven decided to pick.

A hand-drawn tree visually explains dependency management, showing selection of commons:lib 2.0 over 1.2.

This isn't just a minor inconvenience. An unexpected version slipping into your build could introduce subtle bugs or, far worse, known security vulnerabilities without you even realising it.

How Maven Resolves Conflicts

Maven’s strategy for picking a winner in a version showdown is called "nearest definition". It’s a simple rule: if two dependencies ask for different versions of the same library, Maven chooses the version that's closer to your project in the dependency tree.

The version that's "further away" (deeper down the tree) gets left out.

Let’s imagine your pom.xml has two direct dependencies:

  • com.example:alpha:1.0, which itself depends on log4j:log4j:1.2.17
  • com.example:beta:1.0, which depends on log4j:log4j:1.2.16

Since both versions of log4j are at the same depth, Maven's choice can be a bit unpredictable. However, if alpha was a direct dependency and beta was a transitive dependency of some other library, the version pulled in by alpha would win every time because it's "nearer" to the root.

Identifying Conflicts in the Tree

The mvn dependency:tree output makes these fights obvious. It explicitly flags the losing version with a message like (omitted for conflict with ...).

Take a look at this real-world snippet from a tree output:

[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.5.4:compile
[INFO] |  - org.springframework.boot:spring-boot-starter-json:jar:2.5.4:compile
[INFO] |     - com.fasterxml.jackson.core:jackson-databind:jar:2.12.4:compile
[INFO] - com.example:another-lib:jar:1.1.0:compile
[INFO]    - com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile (omitted for conflict with 2.12.4)

It’s crystal clear. spring-boot-starter-web brought in jackson-databind:2.12.4, while our another-lib wanted the much older 2.9.8. Maven sided with Spring Boot and neatly marked the other version as omitted.

Forcing a Specific Version With Dependency Management

Sometimes, Maven’s default choice isn't the one you want. When you need to take control, the most reliable and professional way to manage versions is with the <dependencyManagement> section in your pom.xml.

Think of this section as the ultimate authority for dependency versions across your entire project and all its sub-modules. Any version you define here overrides any other version requested transitively, period.

Here is a practical example of how to use it to resolve the jackson-databind conflict from before, forcing an even newer version:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

By adding this block, you're telling Maven, "I don't care what any other library asks for. For jackson-databind, you will always use version 2.13.0."

Using <dependencyManagement> is the single most effective practice for creating stable and predictable builds. It centralises version decisions, making your pom.xml the definitive source of truth and preventing unexpected transitive upgrades.

Despite how critical it is, this feature is often misunderstood. A pop quiz of 1,200 ES developers found that only 59.7% truly understood how <dependencyManagement> works, causing 35.5% to completely miss transitive conflicts. You can read more about the quiz findings and see just how common these blind spots are.

This knowledge gap is exactly why tools like the Maven Enforcer Plugin exist—to catch these conflicts automatically and make sure they don't cause problems down the line.

Integrating Dependency Trees Into Your Security Workflow

A clean dependency tree is much more than a developer's debugging tool—it’s a cornerstone of modern cybersecurity and compliance. Generating a Software Bill of Materials (SBOM) is now standard practice, and the output from mvn dependency:tree is the raw data that feeds the entire process. This is how you bridge the gap between a simple command-line tool and your organisation's high-level security strategy.

The first move is usually exporting the tree into a format that other tools can actually use. While you can just pipe the console output to a text file, dedicated plugins give you far more structured and automated options. That exported data then becomes the input for some seriously powerful security tools.

From Tree to Actionable Intelligence

Once you have a clean, machine-readable list of your project's dependencies, you can plug it into vulnerability scanners and SBOM generators. This is where the real value appears, transforming a basic list into a proactive security weapon.

Two of the most popular Maven plugins for this job are:

  • OWASP Dependency-Check Plugin: This tool takes your dependency tree and runs it against the National Vulnerability Database (NVD) to find known Common Vulnerabilities and Exposures (CVEs). You can even configure it to fail your build if it finds any high-severity vulnerabilities.
  • CycloneDX Maven Plugin: This plugin generates a machine-readable SBOM in the industry-standard CycloneDX format. That SBOM can then be ingested by all sorts of security platforms for continuous monitoring.

Here’s a practical example of how to run the CycloneDX plugin to create an SBOM:

mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom

This command scans your project and generates a bom.json and bom.xml file in your target directory, ready for use in security tools.

Having a solid grip on your project’s dependency tree is a non-negotiable part of robust application security best practices. It's the only way to be sure you aren't unknowingly shipping vulnerable code.

Supporting CRA Compliance with Accurate SBOMs

For any business placing products with digital elements on the EU market, this workflow is no longer optional. The Cyber Resilience Act (CRA) mandates that manufacturers maintain a detailed and accurate SBOM for their products. The mvn dependency:tree command is the absolute starting point for gathering the data needed to meet that obligation.

An accurate dependency graph directly helps you with CRA compliance by:

  1. Providing Transparency: It gives you a complete inventory of every single software component, including all the transitive ones you never added yourself.
  2. Enabling Vulnerability Management: It lets you instantly check if a newly disclosed vulnerability affects your product.
  3. Simplifying Security Audits: It provides crystal-clear documentation for regulators and auditors, proving you've done your due diligence.

Maintaining this visibility is crucial. In the European software manufacturing world, managing Maven dependency trees has become a pillar of CRA readiness. According to a Q3 2025 survey by ENISA of 500 EU-based manufacturers, 72% reported that unmonitored transitive dependencies accounted for 45% of their SBOM inaccuracies during CRA applicability assessments. You can dig into the critical findings and their impact on compliance here.

For organisations getting ready for the CRA, automating the generation and analysis of the mvn dependency:tree output isn't just a best practice—it's a core operational requirement. Getting this wrong can lead to non-compliance and serious business risk.

Integrating this entire dependency analysis process into a continuous integration and continuous delivery (CI/CD) pipeline is a game-changer. By automating these checks, you guarantee that every single build is scrutinised for new dependencies and potential vulnerabilities before it ever gets close to production.

Ultimately, that humble dependency tree becomes a vital source of truth for a robust, automated, and compliant security posture.

Visualizing Large Dependency Trees For Better Insights

When you're dealing with a large, enterprise-scale project, the standard text output from mvn dependency:tree can quickly spiral into a thousand-line monster. Trying to manually eyeball a file that big to spot patterns or risks is next to impossible. This is where visualization is no longer a "nice-to-have"—it's an essential tool that transforms a wall of text into an interactive map, revealing complex relationships at a glance.

The first step is getting the tree into a format that graphical tools can actually understand. The Maven Dependency Plugin supports several output types, but GraphML is one of the most versatile for this job. You can generate it with a single command.

mvn dependency:tree -DoutputType=graphml -DoutputFile=dependency-graph.graphml

This command doesn't print the tree to your console. Instead, it creates a dependency-graph.graphml file right in your project's root directory. This file contains all the nodes (your dependencies) and edges (the relationships between them) needed to build a visual map.

Creating An Interactive Dependency Map

With the GraphML file in hand, you can bring it to life using open-source tools. Software like Gephi or yEd Graph Editor are excellent choices here. Just import your .graphml file, and the tool will render a graphical representation of your entire dependency web.

This visual approach pays off immediately in several ways:

  • Spotting Hub Dependencies: You can instantly identify highly connected "hub" nodes—those core libraries that are relied upon by many other parts of your project. These hubs often represent the greatest risk, as a single vulnerability in one of them has a massive blast radius.
  • Identifying Circular Dependencies: While Maven can handle them, circular dependencies are a classic code smell. They’re often invisible in a text file but show up as clear loops in a graph, giving you a clear signal that some refactoring is needed.
  • Communicating with Stakeholders: A visual graph is a powerful tool for explaining your software's structure to non-technical stakeholders, which is invaluable during security reviews or project planning meetings.

This diagram shows how visualizing your dependency tree fits into a modern security and compliance workflow.

A security workflow diagram showing Mvn Tree, SBOM Generator, and Compliance Report steps.

As you can see, the tree data is the raw input that feeds directly into critical compliance outputs like SBOMs and vulnerability reports.

The sheer scale of modern dependency graphs makes visualization a necessity, not a luxury. In the ES region, where IoT vendors make up 28% of digital product placements, analysis shows 88% of Maven trees in embedded projects exceed 150 nodes.

This complexity is precisely why graphical analysis is so valuable. The Maven Central Dependency Graph, an open dataset totalling 3.1 GB, is a critical resource for manufacturers auditing supply chains for compliance with regulations like the CRA. You can learn more about the Maven graph findings for more detail.

Ultimately, generating a clear SBOM from this data is a key step in the process. You can learn more about CRA SBOM requirements in our article.

Common Questions About Mvn Dependency Tree

As you get deeper into Maven, a few questions about mvn dependency:tree tend to pop up again and again. Getting the answers straight can save you hours of pain when you're debugging a tricky build or trying to lock down your project's security.

Let's walk through some of the most common ones I hear from developers.

How Do I Find Where a Transitive Dependency Came From?

This is probably the number one question. You've got a library in your project—say, an old version of commons-logging—and you have no idea which of your direct dependencies pulled it in. Hunting it down manually is a nightmare.

The quickest way to solve this is with the -Dverbose flag. It tells Maven to show you the full story of how it resolved each dependency, including the ones it considered but ultimately left out.

For a really targeted search, you can combine it with a filter. This is a game-changer. Instead of wading through a massive tree, you can pinpoint the exact library you're curious about.

# Example: Find out what brought in commons-logging
mvn dependency:tree -Dverbose -Dincludes=commons-logging:commons-logging

Just like that, you get a clear trail right from your pom.xml to the specific library causing the problem. What could have been a long manual search becomes a single, precise command.

Why Does the Tree Output Look Different From My Final Build?

Another common point of confusion is seeing versions in the dependency tree that don't actually end up in your final JAR or WAR file. It can be a bit jarring at first.

Think of it this way: mvn dependency:tree is a diagnostic tool. It shows you the entire decision-making process, including all the different versions that were candidates and why certain ones were rejected due to Maven's "nearest definition" conflict resolution rule.

A practical example is seeing a line like (omitted for conflict with 1.2.0) in your verbose tree output. This tells you that version was considered but lost the conflict. Your final build artifact will only contain the winning version, 1.2.0.

The tree shows the why—the logic behind version selection—while the final built artifact contains the result. The result is the single, winning version of each library that Maven actually selected and packaged.

Can I Use the Tree to Find Security Vulnerabilities?

Not directly, no. The command's job is to visualise relationships between libraries; it has zero built-in security intelligence. It can't tell you if a specific version of a library has a known vulnerability.

However, the output from dependency:tree is the essential first step for any real security scanning. You need that clean, resolved list of dependencies to feed into a proper security tool.

A common workflow is to use a dedicated plugin like the OWASP Dependency-Check Maven plugin. That tool takes the dependency list, checks it against databases of known vulnerabilities (like CVEs), and gives you back a security report you can actually act on.

Here is a practical example of how you would run it from the command line:

mvn org.owasp:dependency-check-maven:check

After running, it generates an HTML report in target/dependency-check-report.html that lists all found vulnerabilities.


Navigating compliance requirements like the Cyber Resilience Act requires a deep understanding of your software's dependencies. Regulus provides a clear roadmap to help you prepare for EU regulations, turning complex obligations into an actionable plan. Gain clarity on your CRA requirements by visiting https://goregulus.com.

More
Regulus Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.