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:
spotless:checkfails if a file is not formatted and prints the differences.spotless:applyrewrites the files in place.
The project
Our project prints the areas of a few shapes. Its source is formatted carelessly.
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.
<?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.
<?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.
<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.
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
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
List all Java tutorials.