Writing a custom rule

Custom rules are easy to make with the maven-enforcer-rule-api. These rules can then be invoked with the maven-enforcer-plugin.

Note: The files shown below may be downloaded here: custom-rule.zip

Project with custom Enforcer Rule

First make a new jar project starting with the sample pom below:

<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>custom-rule</groupId>
  <artifactId>custom-rule-sample</artifactId>
  <version>1.0</version>

  <name>My Custom Rule</name>
  <description>This is my custom rule.</description>

  <properties>
    <api.version>3.5.0</api.version>
    <mavenVersion>3.6.3</mavenVersion>
    <!-- use JDK 1.8 or 11 -->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven.enforcer</groupId>
      <artifactId>enforcer-api</artifactId>
      <version>${api.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-core</artifactId>
      <version>${mavenVersion}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <!-- generate index of project components -->
        <groupId>org.eclipse.sisu</groupId>
        <artifactId>sisu-maven-plugin</artifactId>
        <version>0.9.0.M1</version>
        <executions>
          <execution>
            <goals>
              <goal>main-index</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Target bytecode version of rule must be 1.8 or 11 - due to MENFORCER-473.

Note that the classloader is shared with the embedding maven-enforcer-plugin (a regular plugin classloader) and therefore the artifacts org.apache.maven.enforcer:enforcer-api are always loaded in the same version as the embedding maven-enforcer-plugin.

Custom rule artifacts should therefore only depend on enforcer-api and core Maven artifacts with provided scope (for details refer to MNG-7097).

The classes available from enforcer-rules must not be used from custom rules, as those are not considered API and may change in backwards-incompatible ways with every maven-enforcer version (even minor ones).

Other dependencies used by custom rule at run time should have compile scope.

Implementation of custom Enforcer Rule

The rule must extend AbstractEnforcerRule (available since API version 3.2.1) and implement its execute method.

Add annotation @Named("yourRuleName") to your Rule class. Your Rule name must start with lowercase character.

In addition, the rule can provide a setter method or simply field for each parameter allowed to be configured in the pom.xml file (like the parameter shouldIfail shown example).

Maven component can be injected into Rule by annotation @Inject on field or constructor.

Entry point for Rule executing is execute method, tf the rule succeeds, it should just simply return. If the rule fails, it should throw an EnforcerRuleException with a descriptive message telling the user why the rule failed. Enforcer plugin takes decision based on configuration and Enforcer Rule level whether build should pass or fail. In case when you want to brake build immediately, execute method can throw an EnforcerRuleError.

Here's a sample class:

package org.example.custom.rule;

import javax.inject.Inject;
import javax.inject.Named;

import java.util.List;

import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.apache.maven.rtinfo.RuntimeInformation;

/**
 * Custom Enforcer Rule - example
 */
@Named("myCustomRule") // rule name - must start from lowercase character
public class MyCustomRule extends AbstractEnforcerRule {

    /**
     * Simple param. This rule fails if the value is true.
     */
    private boolean shouldIfail = false;

    /**
     * Rule parameter as list of items.
     */
    private List<String> listParameters;

    // Inject needed Maven components

    @Inject
    private MavenProject project;

    @Inject
    private MavenSession session;

    @Inject
    private RuntimeInformation runtimeInformation;

    public void execute() throws EnforcerRuleException {

        getLog().info("Retrieved Target Folder: " + project.getBuild().getDirectory());
        getLog().info("Retrieved ArtifactId: " + project.getArtifactId());
        getLog().info("Retrieved Project: " + project);
        getLog().info("Retrieved Maven version: " + runtimeInformation.getMavenVersion());
        getLog().info("Retrieved Session: " + session);
        getLog().warnOrError("Parameter shouldIfail: " + shouldIfail);
        getLog().info(() -> "Parameter listParameters: " + listParameters);

        if (this.shouldIfail) {
            throw new EnforcerRuleException("Failing because my param said so.");
        }
    }

    /**
     * If your rule is cacheable, you must return a unique id when parameters or conditions
     * change that would cause the result to be different. Multiple cached results are stored
     * based on their id.
     * <p>
     * The easiest way to do this is to return a hash computed from the values of your parameters.
     * <p>
     * If your rule is not cacheable, then you don't need to override this method or return null
     */
    @Override
    public String getCacheId() {
        //no hash on boolean...only parameter so no hash is needed.
        return Boolean.toString(shouldIfail);
    }

    /**
     * A good practice is provided toString method for Enforcer Rule.
     * <p>
     * Output is used in verbose Maven logs, can help during investigate problems.
     *
     * @return rule description
     */
    @Override
    public String toString() {
        return String.format("MyCustomRule[shouldIfail=%b]", shouldIfail);
    }
}

Using custom Rule

  • Build and Install or Deploy your custom rule.
  • Add your custom-rule artifact as a dependency of the maven-enforcer-plugin in your build.
  • Add your rule to the configuration section of the maven-enforcer-plugin, the name used in @Named annotation of your Rule will be the name of the rule.

That's it. The full plugin config may look like this:

<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>custom-rule</groupId>
  <artifactId>maven-enforcer-plugin-sample-usage</artifactId>
  <version>1</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>3.5.0</version>
        <dependencies>
          <!-- dependencies to your artifact contains rule implementation -->
          <dependency>
            <groupId>custom-rule</groupId>
            <artifactId>custom-rule-sample</artifactId>
            <version>1.0</version>
          </dependency>
        </dependencies>
        <executions>
          <execution>
            <id>enforce</id>
            <configuration>
              <rules>
                <!-- rule name -->
                <myCustomRule>
                  <!-- rule parameters -->
                  <shouldIfail>true</shouldIfail>
                  <listParameters>
                    <item>item 1</item>
                    <item>item 2</item>
                  </listParameters>
                </myCustomRule>
              </rules>
            </configuration>
            <goals>
              <goal>enforce</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>