Maven Enforcer Plugin: Require Java Version, Maven and OS

The Maven Enforcer Plugin stops a build with a clear message when the JDK, the Maven version or the operating system does not match. Learn requireJavaVersion, requireMavenVersion, requireOS, version ranges, custom messages and skips.

The Maven Enforcer Plugin checks rules about the build environment, such as the minimum Java version, the Maven version and the operating system, and stops the build with a clear message when a rule fails. It runs before any code compiles, so a wrong JDK fails in the first second and not in the middle of the build.

We use the Maven Enforcer Plugin in team projects and CI pipelines, where every developer and build server should build with the same JDK and Maven versions.

The following example requires JDK 25 or newer, Maven 3.9.6 or newer and a Unix-like operating system (Linux or macOS).

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>3.6.3</version>
  <executions>
    <execution>
      <id>enforce-build-environment</id>
      <goals>
        <goal>enforce</goal>
      </goals>
      <configuration>
        <rules>
          <requireJavaVersion>
            <version>25</version>         <!-- JDK 25 or newer -->
          </requireJavaVersion>
          <requireMavenVersion>
            <version>3.9.6</version>      <!-- Maven 3.9.6 or newer -->
          </requireMavenVersion>
          <requireOS>
            <family>unix</family>         <!-- Linux or macOS -->
          </requireOS>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

When a developer builds the project with JDK 21, the build stops at the enforce goal with this message.

[ERROR] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion failed with message:
[ERROR] Detected JDK /usr/lib/jvm/java-21-openjdk-amd64 is version 21.0.12-1 which is not in the allowed range [25,).

Notice that the single value 25 means “25 or newer”. Next, we look at each rule with real failure output, at version ranges for an upper limit, and at custom messages, warnings and skipping the checks.

1. Why Enforce the Java Version

Without the Maven Enforcer Plugin, a project still fails on an old JDK, but much later and with a message that does not name the cause. For example, the invoice-service project compiles with maven.compiler.release set to 25, the way we set the Java version in Maven. On JDK 21, the compiler fails.

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile (default-compile) on project invoice-service: Fatal error compiling: error: release version 25 not supported -> [Help 1]

A new teammate sees this release version not supported error and starts to check the POM, the IDE settings and the compiler plugin. The real problem is that JAVA_HOME points to JDK 21. The enforcer message from the intro names the detected JDK, its path and the allowed range, so the teammate knows what to change at once.

The enforce goal runs in the validate phase, which is the first phase of the build lifecycle, so the rules run before resources are copied and before any code compiles. A failing rule stops the build there, which saves time in a large multi-module build.

2. Maven Enforcer Plugin Example

The following example is the invoice-service project, a small Java 25 library with one Invoice record. We add the plugin from the intro to its pom.xml and build it with Maven 3.9.16 and maven-enforcer-plugin 3.6.3.

On a matching environment, the plugin prints one line per rule, and the build goes on.

$ mvn clean verify
[INFO] --- enforcer:3.6.3:enforce (enforce-build-environment) @ invoice-service ---
[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
[INFO] Rule 1: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed
[INFO] Rule 2: org.apache.maven.enforcer.rules.RequireOS passed
[INFO]
[INFO] --- resources:3.4.0:resources (default-resources) @ invoice-service ---
...
[INFO] BUILD SUCCESS

The plugin numbers the rules in the order of the rules element, starting at 0. A failure message names the rule number and the rule class, so we can find the rule in the POM.

2.1. Requiring a Minimum Java Version

The requireJavaVersion rule compares the version of the JDK that runs Maven with the allowed range. The JDK is the one in JAVA_HOME, or the first java on the PATH when JAVA_HOME is not set.

<requireJavaVersion>
  <version>25</version>
</requireJavaVersion>

Before the comparison, the rule turns the JDK version string into a three-part number, which it calls normalization. For example, the JDK 21 build 21.0.12.1 becomes 21.0.12-1, as the failure message in the intro shows. For Java 8, the value 8 is changed to 1.8, because Java 8 reports its version as 1.8.0_x.

2.2. Requiring a Minimum Maven Version

The requireMavenVersion rule checks the version of Maven that runs the build. We use it when the build needs a feature or a bug fix of a newer Maven release, for example when a plugin in the build requires Maven 3.9.

<requireMavenVersion>
  <version>3.9.12</version>
</requireMavenVersion>

With Maven 3.9.11, the rule fails and prints the detected version.

[ERROR] Rule 1: org.apache.maven.enforcer.rules.version.RequireMavenVersion failed with message:
[ERROR] Detected Maven Version: 3.9.11 is not in the allowed range [3.9.12,).

If Maven is installed on each machine by hand, the versions drift apart over time. The Maven Wrapper (mvnw) downloads the version the project needs, and the requireMavenVersion rule catches the developers who still call an old mvn from the PATH.

2.3. Requiring an Operating System

The requireOS rule checks the operating system of the build machine. It accepts the family, name, version and arch parameters, and the rule passes only when all given parameters match.

We need it when a build step works on one platform only. For example, the jpackage tool of the JDK creates a Windows installer only on Windows, so a profile that builds the .msi file of invoice-service fails on a Linux build server with a long, unclear error. With requireOS, the build stops at once.

<requireOS>
  <family>windows</family>
  <message>The invoice-service installer build runs only on Windows.</message>
</requireOS>

On Linux, the rule prints our message.

[ERROR] Rule 2: org.apache.maven.enforcer.rules.RequireOS failed with message:
[ERROR] The invoice-service installer build runs only on Windows.

Without the message element, the rule prints the detected values and the rule that did not match.

[ERROR] Rule 2: org.apache.maven.enforcer.rules.RequireOS failed with message:
[ERROR] OS Arch: amd64 Family: unix Name: linux Version: 6.18.44-fc-v64 is not allowed by Family=windows

The family parameter accepts windows, unix, mac, dos, win9x, netware, os/2, tandem, z/os and os/400. Note that macOS matches both mac and unix. A ! in front of a value negates it, so !windows allows every system except Windows.

To see the values of the current machine, we run mvn –version. The last line shows the name, version, architecture and family that the rule compares.

$ mvn --version
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
...
OS name: "linux", version: "6.18.44-fc-v64", arch: "amd64", family: "unix"

3. Using Version Ranges

A single version in requireJavaVersion or requireMavenVersion is a minimum. For an upper limit or an exact version, we write a range in the enforcer range syntax. A square bracket includes the boundary, and a round bracket excludes it.

The range compares the normalized version, which has three parts, so 25 in a range means 25.0.0 and not “any JDK 25”. The last column shows the result for our JDK, which reports version 25.0.4-1.

ValueAllowed versionsJDK 25.0.4
2525.0.0 or newerpasses
[25,)25.0.0 or newer (same as 25)passes
[21,26)21.0.0 up to, but not including, 26.0.0passes
[21,25]21.0.0 up to and including 25.0.0fails
[25]only 25.0.0fails
(,25),(25,)every version except 25.0.0passes

For an upper limit, we always use a round bracket with the next major version, such as [21,26), because [21,25] rejects every JDK 25 update release. An upper limit makes sense when a tool in the build does not run on newer JDKs yet. For example, a code generation plugin supports JDK 21 to 25, and JDK 26 breaks it, so we allow [21,26). With this range, JDK 21 and JDK 25 both pass the rule.

<requireJavaVersion>
  <version>[21,26)</version>
</requireJavaVersion>

We use an upper limit only for a known problem, because every new JDK release would otherwise need a POM change.

4. Custom Messages, Warnings and Skipping

Every rule accepts a message element, and the plugin and its rules have settings that change what happens on a failure. We use them to make the failure easier to act on, or to relax a rule for a while.

4.1. Writing a Custom Message

A custom message replaces the default text, so it should tell the developer what to do, not only what is wrong. In this example, the message names the JDK version and the variable to change.

<requireJavaVersion>
  <version>25</version>
  <message>invoice-service needs JDK 25. Set JAVA_HOME to a JDK 25 installation.</message>
</requireJavaVersion>
[ERROR] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion failed with message:
[ERROR] invoice-service needs JDK 25. Set JAVA_HOME to a JDK 25 installation.

The custom message hides the detected version, which the default message shows. We keep the default message in projects where developers know the rule well, and add a custom one when newcomers build the project.

4.2. Logging a Warning Instead of Failing

The level element of a rule accepts ERROR (the default) or WARN. With WARN, a failed rule prints a warning and the build goes on. We use it when we introduce a new rule and want to see how many builds break before we make it strict.

<requireOS>
  <level>WARN</level>
  <family>windows</family>
</requireOS>
[WARNING] Rule 2: org.apache.maven.enforcer.rules.RequireOS warned with message:
OS Arch: amd64 Family: unix Name: linux Version: 6.18.44-fc-v64 is not allowed by Family=windows
...
[INFO] BUILD SUCCESS

To turn all rules into warnings at once, we set the fail parameter of the plugin to false.

4.3. Skipping the Rules

The enforce goal has two user properties for skipping checks from the command line.

  • The property enforcer.skip skips all rules of the plugin.
  • The property enforcer.skipRules skips the named rules only, and the other rules still run. It is available since version 3.2.0.
$ mvn clean verify -Denforcer.skip
[INFO] --- enforcer:3.6.3:enforce (enforce-build-environment) @ invoice-service ---
[INFO] Skipping Rule Enforcement.
[INFO] BUILD SUCCESS

$ mvn clean verify -Denforcer.skipRules=requireOS
[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
[INFO] Rule 1: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed
[INFO] BUILD SUCCESS

Skipping helps when a developer has to build on another machine for a moment, for example to test a fix. A CI pipeline should never skip the rules, because the rules protect the release builds.

5. Maven Enforcer Plugin FAQs

5.1. What Is the Difference Between maven.compiler.release and requireJavaVersion?

The maven.compiler.release property tells the compiler which Java version the bytecode and the API must match, whereas requireJavaVersion checks the JDK that runs the build. A JDK can compile for an older release, so JDK 25 with release set to 21 works. A JDK cannot compile for a newer release, which causes the release version 25 not supported error from section 1. We use both settings together, with the same version.

5.2. How Do I Apply the Rules to All Projects?

We put the plugin configuration in a parent POM that all projects of the team inherit. Each child project gets the rules without repeating them, and a child can still override a value, such as the Java version, in its own POM.

5.3. Which Other Rules Does the Maven Enforcer Plugin Have?

The plugin has more than 30 built-in rules. For the build environment, requireJavaVendor checks the JDK vendor, and requireEnvironmentVariable checks that a variable is set. For dependencies, dependencyConvergence fails the build when two paths bring different versions of one library, and requirePluginVersions fails it when a plugin has no explicit version.

6. Conclusion

The Maven Enforcer Plugin turns assumptions about the build machine into checks that run in the validate phase, before any code compiles. A wrong JDK, an old Maven or the wrong operating system stops the build with a message that names the problem.

The requireJavaVersion and requireMavenVersion rules take a minimum version or a range, and requireOS takes the OS family, name, version and architecture. A custom message tells the developer what to fix, and WARN level and enforcer.skipRules help when we introduce a rule or need an exception for one build.

7. References

Happy Learning !!

Leave a Comment

  1. Hi,

    Maven rules are case sensitive. So the rules used in the above source code will throw error. Hope updating that would help others as I spent more than an hour to figure that out.

    should be

    Thanks,
    Kumar

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.