When -DskipTests is not fast enough: skip Maven mojos properly

-DskipTests is a well known way to skip the execution of unit tests during a Maven build and thus save time. This is handy e.g. after pulling the changes from a team git repository which are supposed to have been tested by a CI already.

This kind of skipping is not specific to maven-surefire-plugin. Many other plugins have similar options: Enforcer has -Denforcer.skip, License Maven plugin has -Dlicense.skip, etc. The more you want to build quickly, without running needless mojos, the more you search for these skip parameters.

Some candidates for skipping, like unit tests and integration tests, are pretty obvious. Other may get revealed by profiling your build either by using a stock Java profiler or by leveraging Maven specific tools like maven-buildtime-profiler or Gradle build scans for Maven.

At some point you are skipping every mojo execution that is not essential to your build. You create a dedicated Maven profile listing all those skip flags, so that you do not need to remember and type them by hand.

But what if that’s still not fast enough? Say that you work on a source tree that has 1200+ modules and

$ mvn clean install -DskipTests -Denforcer.skip -Dformatter.skip -Dimpsort.skip -Dquarkus.build.skip -Dgroovy.skip
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:53 min

is not something you are happy about. How can this be made even faster?

Well, common sense (or further profiling with a Java profiler) may unveil that the named skip* properties do not prevent the skipped mojos' classes from being loaded, initialized, instantiated, configured and their execute() method from being invoked (that’s where the skip* parameters are actually being evaluated). That might be quite a lot of superfluous CPU cycles and luckily, Maven offers a way how to avoid them.

The trick is to remove the skippable mojos from the Maven execution plan altogether. How can that be done? - the key is in playing with the <phase> to which the given mojo is bound. There are two ways how to reach that some mojo is bound to no phase:

  1. By setting <phase>none</phase> or shortly <phase/>

  2. By moving the given plugin definition from the the default profile-less <build> section to a profile.

The option 1. needs to be used for mojos that Maven binds to some phase by default. E.g. the test mojo of surefire-maven-plugin is bound to the test phase by default. The option 2. may be used for all other mojos.

Once we manage it to remove a mojo from the execution plan, its classes will neither be loaded, initialized, instantiated, configured nor their execute() methods will be called.

Let’s have a look at an example. Say, that we want all the tests and source tree validation to happen in the default "profile-less" build (mvn verify) but we want them all to be skipped when building with the -Dquickly property.

To implement that, we need a profile. Surprisingly the profile is not going to exclude stuff when -Dquickly is present, it will rather include the stuff in the default "profile-less" build. To make -Dquickly to work the way we want, the profile will be enabled by default and disabled when -Dquickly is present. This can be done using Maven’s "negated" property profile activation: <property><name>!quickly</name></property>.

This is what it looks like for surefire-maven-plugin and maven-enforcer-plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <executions>
                <execution>
                    <id>default-test</id>
                    <phase/><!-- disable here but enable in the full profile -->
                </execution>
            </executions>
        </plugin>

        <!-- We do not need to disable enforcer here, because -->
        <!-- Maven does not bind it to any phase by default -->

    </plugins>
</build>
<profiles>
    <profile>
        <!-- Tests and sanity checks are disabled when building with '-Dquickly' -->
        <id>full</id>
        <activation>
            <property>
                <name>!quickly</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>default-test</id>
                            <!-- assigned to a phase only if the full profile is active -->
                            <phase>test</phase>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <!-- Enforcer is included in the execution plan only if -->
                    <!-- the full profile is active -->
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-enforcer-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>my-enforcer-rules</id>
                            <goals>
                                <goal>enforce</goal>
                            </goals>
                            <configuration>
                                ...
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

After applying this trick to all non-essential mojos in the source tree with 1200+ modules mentioned above, the quick build time goes down from 03:53 to 2:44:

mvn clean install -Dquickly
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:44 min

Who would expect such a big speedup?

P.S.: You may want to try mvnd, the Maven Daemon to reduce your build times even further.