ZetCode

Maven Spotless

last modified November 13, 2026

In this article we format Java sources and POM files with the Spotless Maven plugin. We check the formatting with spotless:check, fix it with spotless:apply, make the build fail on unformatted code, and limit the check to changed files. The examples use Maven 3.10, Spotless 3.11.0, google-java-format 2.0.0, and Java 25.

Spotless is a formatting tool from DiffPlug with plugins for Maven and Gradle. It does not format code by itself; it runs formatters such as google-java-format, Palantir Java Format, or the Eclipse formatter, and adds simple steps like removing unused imports or trimming trailing whitespace. A formatter settles the discussions about code style: the code looks the same no matter who wrote it, and reviews show only real changes.

The plugin has two main goals:

The project

Our project prints the areas of a few shapes. Its source is formatted carelessly.

shapes/src/main/java/com/example/shapes/Main.java
package com.example.shapes;

import java.util.List;
import java.util.Map;
import java.util.ArrayList;

public class Main {
  public static void main(String[] args)
  {
    List<Shape> shapes = List.of(new Circle(1.5), new Rectangle(2,3),new Circle( 0.5 ));
      for (Shape s: shapes) {
          System.out.printf("%-10s %8.3f%n", s.getClass().getSimpleName(), s.area());
      }
    double total = shapes.stream().mapToDouble(Shape::area).sum();
    System.out.printf("Total area: %.3f%n",total);
  }
}

sealed interface Shape permits Circle, Rectangle { double area(); }

record Circle(double radius) implements Shape {
    public double area() { return Math.PI*radius*radius; }
}

record Rectangle(double width, double height) implements Shape {
        public double area() {
            return width * height;
        }
}

The file mixes two-space, four-space, and eight-space indentation, puts a brace on its own line, has missing spaces around operators and commas, and imports two classes it does not use.

shapes/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>shapes</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <java>
                        <googleJavaFormat/>
                        <removeUnusedImports/>
                    </java>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

The plugin has the group ID com.diffplug.spotless. Its configuration has a section for each file type; the java section formats files in src/main/java and src/test/java.

<googleJavaFormat/>
<removeUnusedImports/>

We use two steps: google-java-format formats the code according to the Google Java Style Guide, and removeUnusedImports deletes imports that the code does not use.

Checking the formatting

We run the check goal from the command line. On Java 25, Maven also prints warnings about terminally deprecated sun.misc.Unsafe methods called by the Spotless library; we have left them out of the output.

The check fails and shows the differences between the file and its formatted version as a unified diff. Removed lines start with -, added lines with +, and the · character marks spaces, so that differences in whitespace are visible.

$ mvn spotless:check
Spotless.Java is keeping 1 files clean - 1 needs changes to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD FAILURE
The following files had format violations:
    src/main/java/com/example/shapes/Main.java
        @@ -1,29 +1,30 @@
         package·com.example.shapes;
         
         import·java.util.List;
        -import·java.util.Map;
        -import·java.util.ArrayList;
         
         public·class·Main·{
        -··public·static·void·main(String[]·args)
        -··{
        -····List<Shape>·shapes·=·List.of(new·Circle(1.5),·new·Rectangle(2,3),new·Circle(·0.5·));
        -······for·(Shape·s:·shapes)·{
        -··········System.out.printf("%-10s·%8.3f%n",·s.getClass().getSimpleName(),·s.area());
        -······}
        +··public·static·void·main(String[]·args)·{
        +····List<Shape>·shapes·=·List.of(new·Circle(1.5),·new·Rectangle(2,·3),·new·Circle(0.5));
        +····for·(Shape·s·:·shapes)·{
        +······System.out.printf("%-10s·%8.3f%n",·s.getClass().getSimpleName(),·s.area());
        +····}
         ····double·total·=·shapes.stream().mapToDouble(Shape::area).sum();
...

Applying the formatting

The apply goal formats the files in place. Then we check again.

The file is formatted with two-space indentation, which is the Google style, and the unused imports are gone. The second check passes. Spotless remembers the files that were clean in the target/spotless-index file and skips them while they are unchanged; the message says one file was skipped because of this cache.

$ mvn spotless:apply
Spotless.Java is keeping 1 files clean - 1 were changed to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD SUCCESS
$ cat src/main/java/com/example/shapes/Main.java
package com.example.shapes;

import java.util.List;

public class Main {
  public static void main(String[] args) {
    List<Shape> shapes = List.of(new Circle(1.5), new Rectangle(2, 3), new Circle(0.5));
    for (Shape s : shapes) {
      System.out.printf("%-10s %8.3f%n", s.getClass().getSimpleName(), s.area());
    }
    double total = shapes.stream().mapToDouble(Shape::area).sum();
    System.out.printf("Total area: %.3f%n", total);
  }
}

sealed interface Shape permits Circle, Rectangle {
  double area();
}

record Circle(double radius) implements Shape {
  public double area() {
    return Math.PI * radius * radius;
  }
}

record Rectangle(double width, double height) implements Shape {
  public double area() {
    return width * height;
  }
}
$ mvn spotless:check
Spotless.Java is keeping 1 files clean - 0 needs changes to be clean, 0 were already clean, 1 were skipped because caching determined they were already clean
BUILD SUCCESS

Configuration

We pin the formatter version, switch to four-space indentation, add formatting of the POM, and run the check in every build.

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>shapes</artifactId>
    <version>1.0.0</version>

    <build>
        <plugins>
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <java>
                        <googleJavaFormat>
                            <version>2.0.0</version>
                            <style>AOSP</style>
                        </googleJavaFormat>
                        <removeUnusedImports/>
                    </java>
                    <pom>
                        <sortPom>
                            <nrOfIndentSpace>4</nrOfIndentSpace>
                        </sortPom>
                    </pom>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>6.1.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.release>25</maven.compiler.release>
    </properties>
</project>

The POM has its properties and dependencies sections at the end, after the build section, which is not the usual order.

<googleJavaFormat>
    <version>2.0.0</version>
    <style>AOSP</style>
</googleJavaFormat>

Every Spotless release has a default version of each formatter. Pinning the version keeps the formatting stable when we upgrade the plugin; a new formatter version can change the output and make the check fail on code nobody touched. The AOSP style, used by the Android Open Source Project, indents with four spaces instead of two.

<pom>
    <sortPom>
        <nrOfIndentSpace>4</nrOfIndentSpace>
    </sortPom>
</pom>

The pom section formats the pom.xml file. The sortPom step sorts the POM elements into the recommended order and indents them with four spaces.

<goals>
    <goal>check</goal>
</goals>

The check goal is bound to the verify phase, so mvn verify fails on unformatted files.

The first build fails. The check stopped at the Java sources and did not report the POM; apply then formats both. The diff command compares the POM with a copy we saved before formatting. The sortPom step moved properties and dependencies before build; it also joined the attributes of the project element into one line. The Java code now has four-space indentation, and the next build passes.

$ mvn verify
--- spotless:3.11.0:check (default) @ shapes ---
Spotless.Java is keeping 1 files clean - 1 needs changes to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD FAILURE
    src/main/java/com/example/shapes/Main.java
$ mvn spotless:apply
Spotless.Java is keeping 1 files clean - 1 were changed to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
Spotless.Pom is keeping 1 files clean - 1 were changed to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD SUCCESS
$ diff pom.orig pom.xml
2,4c2
< <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">
---
> <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">
9a8,19
>     <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>
41,52d50
< 
<     <dependencies>
<         <dependency>
<             <groupId>org.junit.jupiter</groupId>
<             <artifactId>junit-jupiter</artifactId>
<             <version>6.1.3</version>
<             <scope>test</scope>
<         </dependency>
<     </dependencies>
<     <properties>
<         <maven.compiler.release>25</maven.compiler.release>
<     </properties>
$ head -12 src/main/java/com/example/shapes/Main.java
package com.example.shapes;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Circle(1.5), new Rectangle(2, 3), new Circle(0.5));
        for (Shape s : shapes) {
            System.out.printf("%-10s %8.3f%n", s.getClass().getSimpleName(), s.area());
        }
        double total = shapes.stream().mapToDouble(Shape::area).sum();
        System.out.printf("Total area: %.3f%n", total);
$ mvn verify
--- spotless:3.11.0:check (default) @ shapes ---
Spotless.Java is keeping 1 files clean - 0 needs changes to be clean, 0 were already clean, 1 were skipped because caching determined they were already clean
Spotless.Pom is keeping 1 files clean - 0 needs changes to be clean, 0 were already clean, 1 were skipped because caching determined they were already clean
BUILD SUCCESS

Formatting only changed files

Introducing a formatter into an existing project reformats every file at once. This causes conflicts in open branches and hides the history of the lines in git blame. The ratchetFrom option limits Spotless to the files that have changed since a Git reference.

pom.xml (configuration)
<configuration>
    <ratchetFrom>HEAD</ratchetFrom>
    <java>
        <googleJavaFormat>
            <version>2.0.0</version>
            <style>AOSP</style>
        </googleJavaFormat>
        <removeUnusedImports/>
    </java>

The option takes a Git reference, such as a branch, tag, or commit. Teams usually use origin/main; files that were not changed in the current branch are never checked or formatted. Our example compares with HEAD, the last commit.

src/main/java/com/example/shapes/Square.java
package com.example.shapes;

record Square(double side) {
  double area() { return side*side; }
}

The project is now a Git repository; its unformatted Main.java is committed. We add a new, also unformatted file.

Spotless checks only Square.java, the one file that differs from the last commit; the old Main.java is left alone. The apply goal also formats only the new file. The spotless.check.skip property skips the check, for example for a quick local build.

$ git status --short
?? src/main/java/com/example/shapes/Square.java
$ mvn spotless:check
Spotless.Java is keeping 1 files clean - 1 needs changes to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD FAILURE
The following files had format violations:
    src/main/java/com/example/shapes/Square.java
        @@ -1,5 +1,7 @@
         package·com.example.shapes;
         
         record·Square(double·side)·{
        -··double·area()·{·return·side*side;·}
        +····double·area()·{
        +········return·side·*·side;
        +····}
         }
$ mvn spotless:apply
Spotless.Java is keeping 1 files clean - 1 were changed to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
BUILD SUCCESS
$ cat src/main/java/com/example/shapes/Square.java
package com.example.shapes;

record Square(double side) {
    double area() {
        return side * side;
    }
}
$ mvn verify -Dspotless.check.skip=true
--- spotless:3.11.0:check (default) @ shapes ---
Spotless check skipped
BUILD SUCCESS

Source

Spotless Maven plugin

In this article we have formatted Java sources and the POM with the Spotless Maven plugin, checked the formatting in the build, and limited it to changed files.

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.