ZetCode

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.

archiver/src/main/java/com/example/archiver/Main.java
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.

archiver/pom.xml
<?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.

pom.xml (rules)
<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.

pom.xml (dependencyManagement)
<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.

pom.xml (bannedDependencies rule)
<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.

pom.xml (dependencyManagement)
<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.

pom.xml (requirePluginVersions rule)
<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

RuleChecks
requireJavaVersionversion of the JDK running Maven
requireMavenVersionversion of Maven
dependencyConvergenceone version of each dependency in the tree
requireUpperBoundDepsno dependency below a requested version
bannedDependenciesunwanted dependencies
banDuplicatePomDependencyVersionsduplicate declarations
requirePluginVersionsexplicit plugin versions
requireReleaseDepsno snapshot dependencies
requireOSoperating system
requirePropertya property is set and matches a regex
requireFilesExistfiles 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

Apache Maven Enforcer Plugin

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.