1. Overview
In this tutorial, we’ll learn how to speed up our Maven builds. We’ll present various techniques to optimize the build time and comment on their advantages and drawbacks.
2. General Recommendations
Before any optimization attempt, let’s recall that using the correct Maven phase could save us a lot of time. Why run a full install and pollute our local repository when we only need to compile the code?
On the other hand, in a multi-module project, it’s possible to rebuild only the changed modules and those that depend on them. For instance, if we make changes only in module1 and module2, we can run:
$ mvn clean install -pl module1,module2 -am
3. Using Multiple Threads
By default, the Maven build runs sequentially in a single thread. However, nowadays, all computers have multiple cores. Let’s take advantage of this to use the -T option and build our modules in parallel:
$ mvn clean install -T 1C
- -T 1C means Maven will use one thread per available core.
- -T 4 would force Maven to use four threads.
Last but not least, Maven Reactor ensures that all modules that depend on each other will run sequentially.
4. Optimizing Tests
Tests are a crucial part of software development. Nevertheless, running them less takes a lot of time.
4.1. Running Tests in Parallel
By default, the Surefire plugin runs unit tests sequentially. However, we can configure it to run them in parallel. For instance, to run all tests suites in parallel and use one thread per available core, we’d run:
mvn clean install -Dparallel=all -DperCoreThreadCount=true
However, if we don’t have a lot of unit tests in our project, the overhead cost of parallelization could negate the speed gain.
4.2. Skipping Test Execution
Sometimes we don’t need to run the tests in our local environment. The Maven -DskipTests option skips the test execution while still compiling the test folder:
$ mvn clean install -DskipTests
In a highly-tested project, skipping tests when we don’t need them can save us time!
4.3. Skipping Test Compilation
Additionally, we can skip the test execution and not even compile them by using the -Dmaven.test.skip=true option:
$ mvn clean install -Dmaven.test.skip=true
This method reduces the total build time even more.
5. Optimizing JVM Parameters
By default, HotSpot JVM uses tiered compilation: it uses both the client and server compilers to optimize Java bytecode. This functioning optimizes long-lived processes on a server. However, a build is a succession of short-lived processes. Thus, let’s use the following JVM parameters:
- -XX:-TieredCompilation: the JVM won’t use tiered compilation
- -XX:TieredStopAtLevel=1: the JVM will use the client compiler only
We can have Maven use these options by creating a .mvn/jvm.config file with the following content:
-XX:-TieredCompilation -XX:TieredStopAtLevel=1
Actually, Maven uses caching and incremental build techniques to avoid recompiling unchanged code. Thus, the more new code Maven needs to compile, the more efficient this technique is.
6. Going Offline
Maven makes several round trips to the server during a build, for instance, for dependency resolution and plugin download. In particular, it will check for new snapshot updates every time. We can avoid that by going into offline mode:
$ mvn clean install -o
Obviously, we can’t use offline mode when we need to update some dependencies.
7. Identifying the Bottleneck
If the previous techniques don’t help us to bring back the build time to something acceptable, we can troubleshoot our build thanks to a profiler. First, let’s create a new Maven project. Then, we’ll follow the instructions on the GitHub of the Maven Profiler project to install it. Lastly, we need to add the dependency in our pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-profiler-plugin</artifactId>
<version>1.7</version>
</plugin>
</plugins>
</build>
We can now use it to get more insight into our build timings by launching the command:
$ mvn clean install -Dprofile
Once the build completes, we can find the report as an HTML text inside the .profiler folder. Let’s have a look at it:
As we can see, the profiler lists all plugin executions and records the time they take. The second section lists the downloaded artifacts. This information can help us identify the plugins that take a long time to run while providing little to no value in the target environment.
8. Using Maven Profiles
Once we identify a bottleneck, we can use Maven profiles to skip them on demand and save time. For example, executing integration tests is generally very long. Moreover, running them every time in a local environment isn’t useful.
Let’s add the failsafe plugin configuration in our pom.xml:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
We can now add a profile for skipping them:
<profiles>
<profile>
<id>skipITs</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Thanks to overriding the configuration, with the skipITs profile activated, the integration tests are now skipped:
$ mvn clean install -PskipITs
9. Using the Maven Daemon
The Maven daemon aims at improving Maven builds’ velocity. The daemon is a long-lived background process that remains active even after the build completes. It reduces build overhead by keeping essential components in memory, enabling faster project builds by avoiding repeated startup and initialization processes. To install it, we can follow the instructions on the GitHub page of the project. Let’s now use it to launch the build:
$ mvnd clean install
If the daemon needs to start, the build time is slightly higher. Last but not least, we can combine the previous techniques with the Maven daemon. For instance, we can build via the daemon and skip the tests:
$ mvnd clean install -Dmaven.test.skip=true
Nevertheless, let’s point out that since the Maven daemon is a long-lived process, setting up the JVM parameters as we did previously would probably do more harm than good.
10. Conclusion
In this article, we showcased various techniques to reduce the build time with Maven. In a nutshell, we should decide to start using the Maven daemon. Then, let’s remember to skip tests when we don’t need to run them. We can also profile the build and use Maven profiles to exclude time-consuming low-value tasks. If none of this is enough, we can try other techniques described in the article.
As always, the code is available over on GitHub.