The pom.xml in your repository is a promising fiction. It lists the dependencies you asked for, but the JARs that actually land on your classpath are decided by Maven's transitive resolution, and there are usually five times as many of them as you declared. That gap is where supply-chain risk hides: the Log4j that gave you Log4Shell (CVE-2021-44228) was, for most victims, a transitive dependency pulled in by something else, sitting several layers deep where nobody was looking. Securing a Maven build means controlling not just what you declare, but what resolves, what's verified, and what's continuously checked.
Why is Maven dependency resolution a security problem?
Maven resolves version conflicts by "nearest wins" — the dependency closest to your project in the tree takes precedence, regardless of which version is newer or safer. This means a deeply nested library can silently downgrade a dependency you thought was patched, and nothing in your pom.xml will show it. The build succeeds, the tests pass, and you're running vulnerable code you never chose. The controls below exist to make resolution predictable and auditable rather than incidental.
Pin versions with dependencyManagement
Declare exact versions in a central <dependencyManagement> block (or import a BOM) so the same version is used everywhere, transitively included or not. This removes the ambiguity of nearest-wins for the libraries you care about:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
</dependencies>
</dependencyManagement>
Anything that transitively pulls jackson-databind now gets the version you pinned, closing the downgrade loophole.
Fail the build on version conflicts
The Maven Enforcer plugin turns silent resolution surprises into hard build failures. Require dependency convergence and ban dynamic ranges so a mirror can't feed you an unexpected version:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<dependencyConvergence/>
<banDuplicatePomDependencyVersions/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
dependencyConvergence fails the build when two paths resolve the same artifact to different versions, forcing you to make the decision explicitly rather than letting nearest-wins make it for you.
Verify what you download
A pinned version still trusts that the bytes coming from the repository are the real, untampered artifact. Maven Central requires published artifacts to be PGP-signed, and you should verify those signatures and checksums rather than accepting whatever your mirror returns. Keep an eye on the .md5/.sha1/.asc files Maven pulls alongside each JAR, and treat a checksum mismatch as a build-stopping event. Prefer a curated internal repository (Nexus or Artifactory) with a strict proxy policy over letting every developer machine reach arbitrary public mirrors — this also defends against dependency-confusion attacks where a malicious public package shadows an internal groupId.
Inspect the real tree, then scan it
Before you can secure the tree, you have to see it. The dependency plugin prints what actually resolves:
mvn dependency:tree -Dverbose
Then run a vulnerability scan against that resolved set. The OWASP Dependency-Check plugin is a common starting point:
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS> <!-- fail on High+ -->
</configuration>
</plugin>
The limitation of a raw CVSS gate is that it can't tell whether a flagged dependency is actually reachable from your code — you either fail the build on everything High-and-above (noisy) or set the threshold so high you miss real risk. That's the gap reachability-aware scanning closes.
Don't forget the plugins
Maven's dependency security conversation usually stops at <dependencies>, but the <build><plugins> section is code that executes during your build with full access to the environment. A compromised or outdated plugin — or one pulled from an untrusted mirror — can inject behavior into every build it touches. Pin plugin versions explicitly (never rely on the latest resolved version), source them from your curated repository, and keep the same patch discipline you apply to runtime dependencies. The build lifecycle is part of your attack surface, and plugins are the part most teams never audit. Combined with a locked-down settings.xml that points at a single trusted mirror, this closes the loop between what you compile and what you run.
Maven security checklist
| Control | Command / config | Prevents |
|---|---|---|
| Central version pinning | <dependencyManagement> / BOM | Silent transitive downgrades |
| Convergence enforcement | maven-enforcer-plugin | Nearest-wins surprises |
| Ban dynamic ranges | Enforcer requireReleaseDeps | Unexpected version pulls |
| Artifact verification | PGP/checksum, curated proxy | Tampered/confused artifacts |
| Continuous CVE scan | Dependency-Check / SCA | Vulnerable dependencies |
How Safeguard helps
Every control above assumes you can see and act on the resolved tree continuously — which is exactly where a pom.xml and a periodic manual scan fall short. Safeguard's software composition analysis resolves and inventories the full transitive graph on every build, so a Log4j-style dependency buried four layers deep can't hide from you. Rather than a blunt CVSS gate, its reachability engine tells you which flagged dependencies are actually invokable from your application code, cutting the false-positive noise that makes teams disable build gates. When a safe upgrade exists, auto-fix pull requests open the version bump — often just editing your dependencyManagement block — directly against the repo, and the Safeguard CLI runs the same analysis locally and in CI so nothing merges without it. For continuous inventory and license tracking across builds, SBOM Studio turns each build's resolved tree into a signed, queryable bill of materials.
Start free at app.safeguard.sh/register, and follow the Maven setup guide at docs.safeguard.sh.