Safeguard
Security Guides

Auditing Maven Dependencies with OWASP Dependency-Check

OWASP Dependency-Check is the classic way to scan Java and Maven projects against the NVD. Learn to run it, tame its false positives, and move beyond CPE matching.

Priya Mehta
Security Researcher
Updated 6 min read

Java projects accumulate risk in a way that is easy to underestimate. A single Spring Boot service can resolve well over a hundred JARs through its dependency tree — a spring-webmvc Maven dependency alone pulls in Spring's core, beans, context, and AOP modules before you've added a single library of your own — and the JVM ecosystem has a long history of high-impact library flaws — Log4Shell (CVE-2021-44228) being the one that woke everyone up, buried as it was inside a logging component that almost nobody had declared directly. Auditing that tree of transitive JARs is exactly what OWASP Dependency-Check exists to do, and for many teams it is still the default.

How OWASP Dependency-Check works

Dependency-Check is unusual among package-manager audit tools in that it does not simply match package names against an advisory feed. Instead it inspects each dependency, extracts evidence (group IDs, artifact names, embedded manifest data), and constructs a Common Platform Enumeration (CPE) identifier for it. It then looks that CPE up against the National Vulnerability Database (NVD) to find associated CVEs. This "evidence-based" approach lets it flag vulnerabilities even in bundled or shaded JARs where the coordinate is obscured — but it is also the source of its biggest quirk, which we will get to.

The most common way to run it on a Maven project is the plugin, invoked directly:

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

That downloads (and caches) a local copy of the NVD data, analyzes every resolved dependency, and writes an HTML and XML report under target/. To make it a permanent part of the build, bind the plugin to a phase in your pom.xml and set a threshold that fails the build on a sufficiently severe CVSS score:

<plugin>
  <groupId>org.owasp</groupId>
  <artifactId>dependency-check-maven</artifactId>
  <version>10.0.0</version>
  <configuration>
    <failBuildOnCVSS>7</failBuildOnCVSS>
    <nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
  </configuration>
  <executions>
    <execution>
      <goals><goal>check</goal></goals>
    </execution>
  </executions>
</plugin>

Note the nvdApiKey. Since the NVD tightened its rate limits, running Dependency-Check without an API key is painfully slow — request a free key and pass it in via an environment variable. You can also run the standalone CLI against any directory of artifacts:

dependency-check.sh --project "my-service" --scan ./target --failOnCVSS 7

Managing false positives

Because Dependency-Check matches on CPE evidence rather than exact package identity, it produces false positives — it may map your dependency to a CVE for an unrelated product that happens to share a name. This is expected behavior, not a bug, and the tool ships a mechanism for it: suppression files. When you confirm a finding is a mismatch, you add a suppression entry keyed to the specific file hash or CPE and a regex for the CVE:

<suppress>
  <notes>False positive: unrelated product with same name</notes>
  <packageUrl regex="true">^pkg:maven/com\.example/.*$</packageUrl>
  <cve>CVE-2020-0000</cve>
</suppress>

Point the plugin at the file with <suppressionFiles>. Maintaining this suppression file becomes an ongoing chore, and it is worth version-controlling with review, because a careless suppression can hide a real vulnerability.

Reading the report and tuning the database

The generated report groups findings by dependency, each with its highest CVSS score, the matched CPE, and the evidence that produced the match. When you triage, start from the evidence: a high-confidence coordinate match on a well-known artifact is almost always real, while a match built from a generic vendor string embedded in a manifest is where mismatches hide. It is also worth keeping the local NVD cache fresh — Dependency-Check maintains a database that it updates incrementally, and a stale cache means you audit against yesterday's advisories. In CI, persist that cache between runs so you get the speed benefit without auditing against outdated data, and schedule at least one full update on a regular cadence.

Where it falls short

  • False-positive overhead. CPE matching is broad by design. Teams routinely spend real time curating suppression files, and that maintenance never ends.
  • NVD-only view. Relying on the NVD means missing advisories that appear in the GitHub Advisory Database or ecosystem-specific feeds first, and inheriting the NVD's own enrichment delays.
  • No reachability. Like every version-matching scanner, it reports that a vulnerable JAR is on the classpath — not whether the vulnerable class is ever loaded or the vulnerable method ever called.
  • Snapshot, not a program. It runs, it reports, it forgets. There is no shared triage state, no SLA tracking, and no evidence trail beyond the raw report.

Going further

Dependency-Check is a fine gate to keep, but the false-positive burden and the missing exploitability signal are exactly what a modern platform is built to solve. Safeguard's software composition analysis engine identifies Java dependencies by precise coordinate and advisory, which sidesteps most CPE-mismatch noise, and it adds class- and method-level reachability so a CVE in an unused code path is ranked far below one your service actually invokes.

Because a Maven audit is only as trustworthy as the inventory behind it, pair the scan with an accurate bill of materials from SBOM Studio, which captures the full resolved graph — including shaded and vendored JARs — in a portable CycloneDX document you can hand to auditors or customers. You can run the same analysis in CI and locally through the Safeguard CLI, keeping developer feedback fast while enforcing policy centrally.

If you currently rely on a Nexus/Sonatype-based workflow and are evaluating alternatives, the Safeguard vs Sonatype comparison walks through how reachability and autonomous remediation change the Java audit experience.

Summary table

ConcernOWASP Dependency-CheckWith continuous SCA
Vulnerability sourceNVD (CPE match)Multi-source advisories, exact coordinate
False positivesHigh; manual suppressionLow; precise identity
Exploitability signalNoneClass/method reachability
Triage statePer-run reportPersistent, policy-driven
RemediationManualAutonomous, tested PRs

Run mvn org.owasp:dependency-check-maven:check as your baseline, curate a reviewed suppression file, always supply an NVD API key, and layer reachability-aware scanning so your engineers chase exploitable flaws rather than name collisions.

See how your Java dependency graph looks with reachability context — start free or read the documentation.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.