Four built in Maven Enforcer rules help most: banDynamicVersions, requireReleaseDeps, dependencyConvergence and requireUpperBoundDeps. Together they stop version ranges, snapshots and conflicting transitive versions from reaching a release, so what you tested is what you ship. None of them check for vulnerabilities.
The rules and what they do
| Rule | What it enforces |
|---|---|
| banDynamicVersions | Bans version ranges, LATEST, RELEASE and SNAPSHOT versions |
| requireReleaseDeps | Fails if any snapshot dependency is found |
| dependencyConvergence | Requires one version of each artifact across the whole tree |
| requireUpperBoundDeps | Requires the resolved version to be at least the highest one found transitively |
banDynamicVersions
The Enforcer documentation defines dynamic versions as ranges starting with a bracket or parenthesis, the LATEST and RELEASE placeholders, and versions ending in SNAPSHOT. They can change with each build and need a repository metadata lookup. Options let you allow snapshots, release, latest or ranges, exclude scopes, and ignore specific dependencies.
Why it matters: a dynamic version means the build you run next week may not use the code you reviewed today. That is a supply chain risk, since any new publish under that coordinate range is picked up automatically.
requireReleaseDeps
This rule checks dependencies and fails if snapshots are found. It can check transitive dependencies, which is the default, and the parent. The onlyWhenRelease option runs it only when the project itself is a non-snapshot version, which keeps development builds flexible.
dependencyConvergence
If dependency A and dependency B both depend on artifact C at different versions, the build fails. The documentation shows the fix: exclude one path, or pin a single version through dependencyManagement or a BOM.
requireUpperBoundDeps
Maven normally picks the nearest definition. This rule fails when that choice is lower than a version some other dependency needs. The documented example is slf4j-api resolved at an older version than the one a logging library requires.
A starting configuration
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-supply-chain</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<banDynamicVersions/>
<requireReleaseDeps>
<onlyWhenRelease>true</onlyWhenRelease>
</requireReleaseDeps>
<dependencyConvergence/>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
Pin the plugin version in your own POM and check the current release.
How to adopt without a revolt
- Start with the rules in warning mode if your build tooling allows, or enable one rule per week.
- Fix convergence errors by adding managed versions in one parent POM or an imported BOM.
- Use the ignores option sparingly, and write a comment explaining each exception.
- Run the rules in CI on every pull request, not only at release.
Where they stop
These rules improve repeatability. They do not tell you that a pinned version has an advisory, and they do not check artifact integrity. Add scanning, and consider verified checksums through your repository manager. For how nearest-wins can hide an old version, see why Maven picks an old library version.
Safeguard's dependency scanning and SBOMs cover those gaps for Maven projects, and its gates can block a merge on policy. Free scan: gold.safeguard.sh/scan.
Quick check
Do Enforcer rules detect vulnerable dependencies?
No. They check version hygiene. Use a vulnerability scanner for advisories.
Should snapshots ever be allowed?
In development builds, often yes. The requireReleaseDeps onlyWhenRelease option lets you block them for releases only.
Sources
- Ban Dynamic Versions, Apache Maven Enforcer
- Require Release Dependencies, Apache Maven Enforcer
- Dependency Convergence, Apache Maven Enforcer
- Require Upper Bound Dependencies, Apache Maven Enforcer
Last reviewed: 19 October 2026.