Maven Enforcer Plugin
last modified November 13, 2026
In this article we work with the Maven Enforcer Plugin. We require minimum Java and Maven versions, make the build fail on conflicting dependency versions, ban unwanted dependencies, detect duplicate declarations, and check that all plugins have pinned versions. The examples use Maven 3.10, Enforcer 3.7.0, and Java 25.
The Enforcer plugin checks rules about the build environment and the
project, and fails the build when a rule is broken. It turns conventions
that a team agrees on, such as the required JDK or the ban of a vulnerable
library, into checks that run on every machine and in CI. Its
enforce goal is bound to the validate phase, the
first phase of the build, so a broken rule stops the build before anything
is compiled.
The project
Our application writes a ZIP file with Commons Compress and formats the text with Commons Text.
package com.example.archiver;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.text.WordUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws IOException {
var text = WordUtils.capitalizeFully("hello from the archiver");
try (var zip = new ZipArchiveOutputStream(Path.of("hello.zip"))) {
zip.putArchiveEntry(new ZipArchiveEntry("hello.txt"));
zip.write(text.getBytes(StandardCharsets.UTF_8));
zip.closeArchiveEntry();
}
System.out.println("hello.zip written: " + text);
}
}
The program capitalizes a sentence with WordUtils and writes
it into hello.zip with ZipArchiveOutputStream.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>archiver</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.28.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.15.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.7.0</version>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.9.0,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[25,)</version>
<message>Build with JDK 25 or newer; see README.md.</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The enforce goal runs in an execution that lists the rules.
Each rule is an element named after the rule.
<requireMavenVersion>
<version>[3.9.0,)</version>
</requireMavenVersion>
The requireMavenVersion rule checks the version of Maven
that runs the build. The value is a version range; [3.9.0,)
means 3.9.0 or newer.
<requireJavaVersion>
<version>[25,)</version>
<message>Build with JDK 25 or newer; see README.md.</message>
</requireJavaVersion>
The requireJavaVersion rule checks the JDK that runs Maven.
Every rule accepts a message element, which replaces the
default message and can tell the developer how to fix the problem.
On JDK 25, both rules pass. We then run the build with JDK 21 by setting
the JAVA_HOME variable. The Java rule fails with our message.
Without the rule, the build would fail later in the compiler with the
release version 25 not supported error, which is less clear
and comes only after the dependencies are resolved.
$ mvn validate --- enforcer:3.7.0:enforce (enforce) @ archiver --- Rule 0: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed Rule 1: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed BUILD SUCCESS $ JAVA_HOME=~/.sdkman/candidates/java/21.0.2-amzn mvn validate Rule 0: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed BUILD FAILURE Rule 1: org.apache.maven.enforcer.rules.version.RequireJavaVersion failed with message: Build with JDK 25 or newer; see README.md.
Dependency convergence
Our two dependencies depend on different versions of Commons Lang:
Commons Compress 1.28.0 on 3.18.0 and Commons Text 1.15.0 on 3.20.0.
Maven picks one of them with the nearest-wins strategy, described in the
Maven dependency resolution
article. Here both are equally near and the first declaration wins, so
Commons Text runs with an older Commons Lang than it was built for. We add
the dependencyConvergence rule.
<rules>
<requireMavenVersion>
<version>[3.9.0,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[25,)</version>
<message>Build with JDK 25 or newer; see README.md.</message>
</requireJavaVersion>
<dependencyConvergence/>
</rules>
The rule requires that every dependency resolves to the same version on all paths of the dependency tree.
The rule fails and prints both paths that lead to Commons Lang. We fix the conflict by choosing the version ourselves.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
</dependencies>
</dependencyManagement>
An entry in dependencyManagement sets the version of a
dependency wherever it appears in the tree. We choose the newest one, which
is what both libraries need. The related requireUpperBoundDeps
rule is less strict; it only fails when the resolved version is lower than
a version requested somewhere in the tree.
The second run passes.
$ mvn validate
BUILD FAILURE
Rule 2: org.apache.maven.enforcer.rules.dependency.DependencyConvergence failed with message:
Dependency convergence error for org.apache.commons:commons-lang3:jar:3.18.0. Paths to dependency are:
+-com.example:archiver:jar:1.0.0
+-org.apache.commons:commons-compress:jar:1.28.0:compile
+-org.apache.commons:commons-lang3:jar:3.18.0:compile
and
+-com.example:archiver:jar:1.0.0
+-org.apache.commons:commons-text:jar:1.15.0:compile
+-org.apache.commons:commons-lang3:jar:3.20.0:compile
$ mvn validate
Rule 2: org.apache.maven.enforcer.rules.dependency.DependencyConvergence passed
BUILD SUCCESS
Banned dependencies
The bannedDependencies rule keeps unwanted libraries out of
the project, either directly declared or pulled in transitively. We ban
JUnit 4, Commons Logging, and old versions of Commons Lang, and add a JUnit
4 dependency to see the rule in action.
<bannedDependencies>
<excludes>
<exclude>junit:junit</exclude>
<exclude>commons-logging:commons-logging</exclude>
<exclude>org.apache.commons:commons-lang3:(,3.18.0)</exclude>
</excludes>
<message>Use JUnit 6 instead of JUnit 4 and Commons Lang 3.18.0 or newer.</message>
</bannedDependencies>
Each exclude pattern has the form
groupId:artifactId:version; omitted parts match anything, and
the version may be a range. The (,3.18.0) range matches all
versions below 3.18.0, which fixed a security issue. An
includes element can make exceptions from the excludes.
The rule lists the banned artifact with its path in the tree. Test
dependencies are checked too; the searchTransitive parameter,
true by default, makes the rule check transitive dependencies
as well.
$ mvn validate BUILD FAILURE Rule 3: org.apache.maven.enforcer.rules.dependency.BannedDependencies failed with message: Use JUnit 6 instead of JUnit 4 and Commons Lang 3.18.0 or newer. com.example:archiver:jar:1.0.0 junit:junit:jar:4.13.2 <--- banned via the exclude/include list
Duplicate declarations
The banDuplicatePomDependencyVersions rule detects a
dependency declared twice in the same POM, a mistake that is easy to make
in long POM files. We add a second Commons Lang entry to the
dependencyManagement section and enable the rule.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
</dependencies>
</dependencyManagement>
The two entries ask for different versions of the same artifact.
Maven 3.10 validates POMs more strictly and refuses to read the project before any plugin runs, so the rule never gets a chance. Maven 3.9 only prints a warning and continues, and there the Enforcer rule fails the build. The rule is thus useful for projects that are still built with Maven 3.9 or older.
$ mvn validate 'dependencyManagement.dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-lang3:jar -> version 3.20.0 vs 3.17.0 @ line 22, column 25 The build could not read 1 project -> [Help 1] $ ~/.sdkman/candidates/maven/3.9.11/bin/mvn validate 'dependencyManagement.dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-lang3:jar -> version 3.20.0 vs 3.17.0 @ line 22, column 25 BUILD FAILURE Rule 3: org.apache.maven.enforcer.rules.BanDuplicatePomDependencyVersions failed with message: - dependencyManagement.dependencies.dependency[org.apache.commons:commons-lang3:jar] (2 times)
Plugin versions
A build that relies on the default plugin versions of Maven changes when
Maven is upgraded. The requirePluginVersions rule demands an
explicit version for every plugin used in the build. We add it with the
WARN level.
<requirePluginVersions>
<level>WARN</level>
</requirePluginVersions>
Each rule accepts the level element. With
WARN, a broken rule prints a warning instead of failing the
build, which is handy when a rule is introduced to a large project.
The rule lists the plugins of the default lifecycle that are not pinned,
with the version that Maven 3.10 uses for each. Copying these versions
into the pluginManagement section of the POM, or a parent
POM, makes the rule pass.
$ mvn validate
Rule 3: org.apache.maven.enforcer.rules.RequirePluginVersions warned with message:
Some plugins are missing valid versions or depend on Maven 3.10.0 defaults (LATEST, RELEASE, SNAPSHOT, TIMESTAMP SNAPSHOT as plugin version are not allowed)
org.apache.maven.plugins:maven-compiler-plugin
The version currently in use is 3.16.0 via default lifecycle bindings
org.apache.maven.plugins:maven-surefire-plugin
The version currently in use is 3.6.0 via default lifecycle bindings
org.apache.maven.plugins:maven-jar-plugin
The version currently in use is 3.5.1 via default lifecycle bindings
org.apache.maven.plugins:maven-clean-plugin
The version currently in use is 3.5.0 via default lifecycle bindings
org.apache.maven.plugins:maven-install-plugin
The version currently in use is 3.2.0 via default lifecycle bindings
org.apache.maven.plugins:maven-site-plugin
The version currently in use is 3.22.0 via default lifecycle bindings
org.apache.maven.plugins:maven-resources-plugin
The version currently in use is 3.5.0 via default lifecycle bindings
org.apache.maven.plugins:maven-deploy-plugin
The version currently in use is 3.2.0 via default lifecycle bindings
BUILD SUCCESS
Running rules from the command line
The enforcer:enforce goal can be run directly with a list of
rules in the enforcer.rules property. Rules that need no
configuration can thus check a project without changing its POM. The
enforcer.skip property skips all rules, which can help when
investigating a broken build, but should not end up in CI scripts.
The original POM without dependencyManagement fails the
convergence check from the command line. When the property is set, the
plugin skips the rules configured in the POM.
$ mvn enforcer:enforce -Denforcer.rules=dependencyConvergence,banDuplicatePomDependencyVersions Rule 1: org.apache.maven.enforcer.rules.BanDuplicatePomDependencyVersions passed BUILD FAILURE Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence failed with message: Dependency convergence error for org.apache.commons:commons-lang3:jar:3.18.0. Paths to dependency are: $ mvn validate -Denforcer.skip --- enforcer:3.7.0:enforce (enforce) @ archiver --- Skipping Rule Enforcement. BUILD SUCCESS
Built-in rules
| Rule | Checks |
|---|---|
requireJavaVersion | version of the JDK running Maven |
requireMavenVersion | version of Maven |
dependencyConvergence | one version of each dependency in the tree |
requireUpperBoundDeps | no dependency below a requested version |
bannedDependencies | unwanted dependencies |
banDuplicatePomDependencyVersions | duplicate declarations |
requirePluginVersions | explicit plugin versions |
requireReleaseDeps | no snapshot dependencies |
requireOS | operating system |
requireProperty | a property is set and matches a regex |
requireFilesExist | files exist |
More rules, such as enforceBytecodeVersion, which detects
dependencies compiled for a newer Java than the project targets, come with
the Extra Enforcer Rules from the MojoHaus project, added as a dependency
of the plugin.
Source
In this article we have enforced Java and Maven versions, dependency convergence, banned dependencies, unique declarations, and plugin versions with the Maven Enforcer Plugin.
Author
List all Java tutorials.