ZetCode

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.

shop/pom.xml
<?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.

shop/model/src/main/java/com/example/model/Item.java
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.

shop/pricing/src/main/java/com/example/pricing/Pricing.java
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.

shop/pricing/src/test/java/com/example/pricing/PricingTest.java
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.

.mvn/extensions.xml
<?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:

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.