Maven faster builds
last modified November 13, 2026
In this article we make Maven builds faster. We build the modules of a project in parallel, reuse the results of earlier builds with the Maven build cache extension, and avoid needless work with incremental compilation. The examples use Maven 3.10, build cache extension 1.3.0, and Java 25.
A Maven build spends its time in a few places: starting the JVM, resolving dependencies, compiling, and above all running tests. Each technique in this article attacks a different part. The times in the outputs come from a machine with eight CPU cores; they differ from machine to machine, but the ratios stay similar.
The project
Our shop project has four modules. The model module holds
the data; pricing, shipping, and
invoicing depend on it, but not on each other.
<?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>shop</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>model</module>
<module>pricing</module>
<module>shipping</module>
<module>invoicing</module>
</modules>
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
The parent POM lists the modules and adds JUnit to all of them.
package com.example.model;
public record Item(String name, int priceCents, int weightGrams, int quantity) {
}
The Item record is the only class of the model.
package com.example.pricing;
import com.example.model.Item;
import java.util.List;
public class Pricing {
public static int total(List<Item> items) {
return items.stream().mapToInt(i -> i.priceCents() * i.quantity()).sum();
}
}
The Pricing class computes the total price of the items. The
other two modules are built the same way.
package com.example.pricing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.example.model.Item;
import java.util.List;
import org.junit.jupiter.api.Test;
class PricingTest {
@Test
void sumsPrices() throws InterruptedException {
Thread.sleep(3000);
var items = List.of(new Item("pen", 150, 20, 2), new Item("book", 2490, 600, 1));
assertEquals(2790, Pricing.total(items));
}
}
The tests of the three modules each sleep for three seconds. They stand for slow tests, which most real projects have, such as tests that start a database or a web server.
Sequential build
By default, Maven builds the modules one after another, in the order of the reactor.
The reactor summary shows the time of each module. The build takes about 13 seconds, the sum of the module times.
$ mvn verify Reactor Summary for shop 1.0.0: shop ............................................... SUCCESS [ 0.002 s] model .............................................. SUCCESS [ 2.229 s] pricing ............................................ SUCCESS [ 3.708 s] shipping ........................................... SUCCESS [ 3.610 s] invoicing .......................................... SUCCESS [ 3.597 s] Total time: 13.230 s
Parallel builds
The -T option builds independent modules in parallel. Its
value is the number of threads; a value with the C suffix is
multiplied by the number of CPU cores, so -T 1C means one
thread per core.
Maven still respects the dependencies between the modules. It builds
model first and then the other three modules at the same
time, so the total time drops to less than half. Since the reactor has
at most three modules that can run at once, eight threads are not faster
than four. With -T, the total time is labelled
Wall Clock, because the times of the modules overlap.
Parallel builds need thread-safe plugins; a plugin declares this with the
threadSafe attribute of its goals, as shown in the
Maven custom plugin article. The log of the modules is
interleaved, which makes it harder to read. The
Maven multi-module projects article
explains the reactor in more detail.
$ mvn verify -T 4 Using the MultiThreadedBuilder implementation with a thread count of 4 Reactor Summary for shop 1.0.0: shop ............................................... SUCCESS [ 0.002 s] model .............................................. SUCCESS [ 2.114 s] pricing ............................................ SUCCESS [ 4.100 s] shipping ........................................... SUCCESS [ 4.103 s] invoicing .......................................... SUCCESS [ 4.100 s] Total time: 6.310 s (Wall Clock) $ mvn clean verify -T 1C Using the MultiThreadedBuilder implementation with a thread count of 8 Total time: 6.443 s (Wall Clock)
Incremental compilation
Many developers run mvn clean verify by habit. The
clean phase deletes the target directory, so
everything is compiled again. Without clean, the Compiler
plugin compares the sources with the compiled classes and skips the
compilation when nothing has changed. A changed source file or a changed
dependency triggers recompilation of the module.
We run mvn verify a second time, without clean.
Nothing is compiled, but the build is not much faster, because the tests
run again; Surefire does not know whether the tests are still valid. Only
skipping the tests makes the build fast, which is not what we want in
every build.
$ mvn verify --- compiler:3.16.0:compile (default-compile) @ model --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:testCompile (default-testCompile) @ model --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:compile (default-compile) @ pricing --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:testCompile (default-testCompile) @ pricing --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:compile (default-compile) @ shipping --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:testCompile (default-testCompile) @ shipping --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:compile (default-compile) @ invoicing --- Nothing to compile - all classes are up to date. --- compiler:3.16.0:testCompile (default-testCompile) @ invoicing --- Nothing to compile - all classes are up to date. Total time: 12.455 s $ mvn verify -DskipTests Total time: 1.280 s
Build cache extension
The Maven build cache extension stores the results of a module build. When the inputs of the module do not change, it restores the outputs from the cache and skips the plugins, the tests included. The inputs are the sources, resources, the POM, the plugin configuration, and the dependencies.
<?xml version="1.0" encoding="UTF-8"?>
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.2.0 https://maven.apache.org/xsd/core-extensions-1.2.0.xsd">
<extension>
<groupId>org.apache.maven.extensions</groupId>
<artifactId>maven-build-cache-extension</artifactId>
<version>1.3.0</version>
</extension>
</extensions>
The extension is a core extension of Maven, enabled in the
.mvn/extensions.xml file in the root of the project. Without
further configuration, it caches all modules with default settings.
Options such as remote caches and excluded files are set in the
.mvn/maven-build-cache-config.xml file.
The first build computes a checksum of the inputs of each module, finds no
entry in the cache, builds the module, and saves the result. The cache is
stored in ~/.m2/build-cache by default; we ran the examples
with a different location, set with the
maven.build.cache.location property, so the paths are
shortened.
The second build starts with clean, but all four modules are
restored from the cache. The plugin executions, Surefire included, are
skipped, and the build takes less than a second. On Java 25, the extension
also prints warnings about terminally deprecated
sun.misc.Unsafe methods, which we have left out.
$ mvn verify Cache configuration is not available at configured path .mvn/maven-build-cache-config.xml, cache is enabled with defaults Using XX hash algorithm for cache Attempting to restore project com.example:model from build cache Local build was not found by checksum 2e427f2a7893cf36 for com.example:model Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.036 s -- in com.example.model.ItemTest Saved Build to local file: .../v1.2/com.example/model/2e427f2a7893cf36/local/buildinfo.xml Attempting to restore project com.example:pricing from build cache Local build was not found by checksum 3d5e5517346e11b4 for com.example:pricing Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.041 s -- in com.example.pricing.PricingTest Saved Build to local file: .../v1.2/com.example/pricing/3d5e5517346e11b4/local/buildinfo.xml Attempting to restore project com.example:shipping from build cache Local build was not found by checksum 10cb784157358752 for com.example:shipping Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.040 s -- in com.example.shipping.ShippingTest Saved Build to local file: .../v1.2/com.example/shipping/10cb784157358752/local/buildinfo.xml Attempting to restore project com.example:invoicing from build cache Local build was not found by checksum c568ba2e6b57b8f5 for com.example:invoicing Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.043 s -- in com.example.invoicing.InvoicingTest Saved Build to local file: .../v1.2/com.example/invoicing/c568ba2e6b57b8f5/local/buildinfo.xml Total time: 12.664 s $ mvn clean verify Found cached build, restoring com.example:model from cache by checksum 2e427f2a7893cf36 Skipping plugin execution (cached): surefire:test Found cached build, restoring com.example:pricing from cache by checksum 3d5e5517346e11b4 Skipping plugin execution (cached): surefire:test Found cached build, restoring com.example:shipping from cache by checksum 10cb784157358752 Skipping plugin execution (cached): surefire:test Found cached build, restoring com.example:invoicing from cache by checksum c568ba2e6b57b8f5 Skipping plugin execution (cached): surefire:test Total time: 0.926 s
Rebuilding changed modules
We make a small change in the Shipping class: the
<= operator becomes <. Then we build the
project again.
Only the shipping module has a new checksum, so only its
tests run; the other modules come from the cache. A change in the
model module, even in a comment, would change the checksums
of all four modules, because the others depend on it. The
maven.build.cache.enabled property turns the cache off for a
single build, which then takes the full time again.
$ mvn clean verify Found cached build, restoring com.example:model from cache by checksum 2e427f2a7893cf36 Found cached build, restoring com.example:pricing from cache by checksum 3d5e5517346e11b4 Local build was not found by checksum edb0209ec8a7526d for com.example:shipping Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.043 s -- in com.example.shipping.ShippingTest Found cached build, restoring com.example:invoicing from cache by checksum c568ba2e6b57b8f5 Total time: 5.084 s $ mvn clean verify -Dmaven.build.cache.enabled=false Cache disabled by command line flag, project will be built fully and not cached Build cache is disabled for 'clean' goal. Cache is DISABLED on project level for com.example:shop Cache is DISABLED on project level for com.example:model Cache is DISABLED on project level for com.example:pricing Cache is DISABLED on project level for com.example:shipping Cache is DISABLED on project level for com.example:invoicing Total time: 12.673 s
Other options
More ways to make builds faster:
- Build only part of a multi-module project with
-pl, adding the modules it needs with-am; see the Maven command line article. - Use offline mode,
-o, to skip checks of remote repositories; it works once all dependencies and plugins are in the local repository. - Run tests in parallel inside a module, as shown in the Maven Surefire Plugin article.
- Skip slow checks, such as static analysis, in local builds with their skip properties, and run them in CI.
- Use the Maven Daemon,
mvnd, which keeps a JVM with Maven running between builds and avoids the startup cost.
Source
Apache Maven Build Cache Extension, Parallel builds in Maven 3
In this article we have made Maven builds faster with parallel module builds, incremental compilation, and the build cache extension.
Author
List all Java tutorials.