diff --git a/CHANGES.md b/CHANGES.md
index 9929ccf7a6..76f7a0e6f7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,10 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
## [Unreleased]
+### Added
+- Add a `cleanup` option to `GoogleJavaFormatStep` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
+### Changes
+- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))
## [4.10.4] - 2026-10-08
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index acb1161c04..840d24c3e1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -52,7 +52,7 @@ flexmark-all = "com.vladsch.flexmark:flexmark-all:0.64.8"
freshmark = "com.diffplug.freshmark:freshmark:1.3.1"
cucumber-gherkin = "io.cucumber:gherkin:42.0.1"
gherkin-utils = "io.cucumber:gherkin-utils:12.0.2"
-google-java-format = "com.google.googlejavaformat:google-java-format:1.36.1"
+google-java-format = "com.google.googlejavaformat:google-java-format:2.0.0"
gson = "com.google.code.gson:gson:2.14.0"
json-simple = "org.json:json:20210307"
ktfmt = "org.jetbrains.kotlinx:ktfmt:0.65"
diff --git a/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java
index d13f8d9d40..d07800fbc8 100644
--- a/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java
+++ b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 DiffPlug
+ * Copyright 2023-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package com.diffplug.spotless.glue.java;
+import java.lang.reflect.Method;
import java.util.Objects;
import javax.annotation.Nonnull;
@@ -46,7 +47,7 @@ public class GoogleJavaFormatFormatterFunc implements FormatterFunc {
private final boolean reorderImports;
- public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String style, boolean reflowStrings, boolean reorderImports, boolean formatJavadoc) {
+ public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String style, boolean reflowStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup) {
this.version = Objects.requireNonNull(version);
this.formatterStyle = Style.valueOf(Objects.requireNonNull(style));
this.reflowStrings = reflowStrings;
@@ -56,9 +57,29 @@ public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String st
if (!formatJavadoc) {
builder = builder.formatJavadoc(false);
}
+ if (!cleanup) {
+ builder = disableCleanup(builder);
+ }
this.formatter = new Formatter(builder.build());
}
+ // JavaFormatterOptions.Builder.cleanup(boolean) was added in google-java-format 2.0.0, and this
+ // glue is compiled against an older version.
+ private static JavaFormatterOptions.Builder disableCleanup(JavaFormatterOptions.Builder builder) {
+ Method cleanup;
+ try {
+ cleanup = JavaFormatterOptions.Builder.class.getMethod("cleanup", boolean.class);
+ } catch (NoSuchMethodException e) {
+ // google-java-format versions before 2.0.0 don't have cleanups
+ return builder;
+ }
+ try {
+ return (JavaFormatterOptions.Builder) cleanup.invoke(builder, false);
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
@Override
@Nonnull
public String apply(@Nonnull String input) throws Exception {
diff --git a/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java b/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java
index 079955a731..13b6dde628 100644
--- a/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java
+++ b/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java
@@ -34,6 +34,7 @@ public final class GoogleJavaFormatStep implements Serializable {
private static final boolean DEFAULT_REFLOW_LONG_STRINGS = false;
private static final boolean DEFAULT_REORDER_IMPORTS = false;
private static final boolean DEFAULT_FORMAT_JAVADOC = true;
+ private static final boolean DEFAULT_CLEANUP = true;
private static final String NAME = "google-java-format";
private static final String MAVEN_COORDINATE = "com.google.googlejavaformat:google-java-format";
@@ -44,19 +45,22 @@ public final class GoogleJavaFormatStep implements Serializable {
private final boolean reflowLongStrings;
private final boolean reorderImports;
private final boolean formatJavadoc;
+ private final boolean cleanup;
private GoogleJavaFormatStep(JarState.Promised jarState,
String version,
String style,
boolean reflowLongStrings,
boolean reorderImports,
- boolean formatJavadoc) {
+ boolean formatJavadoc,
+ boolean cleanup) {
this.jarState = jarState;
this.version = version;
this.style = style;
this.reflowLongStrings = reflowLongStrings;
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
+ this.cleanup = cleanup;
}
/** Creates a step which formats everything - code, import order, and unused imports. */
@@ -89,14 +93,22 @@ public static FormatterStep create(String groupArtifact, String version, String
/** Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings. */
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc) {
- return createInternally(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, false);
+ return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, DEFAULT_CLEANUP);
+ }
+
+ /**
+ * Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings.
+ * If {@code cleanup} is false, google-java-format 2.0.0 and later skip their non-whitespace cleanups (older versions don't have any).
+ */
+ public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup) {
+ return createInternally(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, cleanup, false);
}
static FormatterStep createRemoveUnusedImportsOnly(Provisioner provisioner) {
- return createInternally(RemoveUnusedImportsStep.NAME, MAVEN_COORDINATE, defaultVersion(), defaultStyle(), provisioner, defaultReflowLongStrings(), defaultReorderImports(), defaultFormatJavadoc(), true);
+ return createInternally(RemoveUnusedImportsStep.NAME, MAVEN_COORDINATE, defaultVersion(), defaultStyle(), provisioner, defaultReflowLongStrings(), defaultReorderImports(), defaultFormatJavadoc(), defaultCleanup(), true);
}
- private static FormatterStep createInternally(String name, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean removeImports) {
+ private static FormatterStep createInternally(String name, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, boolean cleanup, boolean removeImports) {
Objects.requireNonNull(groupArtifact, "groupArtifact");
if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) {
throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'");
@@ -105,7 +117,7 @@ private static FormatterStep createInternally(String name, String groupArtifact,
Objects.requireNonNull(style, "style");
Objects.requireNonNull(provisioner, "provisioner");
- GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc);
+ GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup);
if (removeImports) {
return FormatterStep.create(name,
step,
@@ -150,8 +162,12 @@ public static boolean defaultFormatJavadoc() {
return DEFAULT_FORMAT_JAVADOC;
}
+ public static boolean defaultCleanup() {
+ return DEFAULT_CLEANUP;
+ }
+
private State equalityState() {
- return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc);
+ return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc, cleanup);
}
private static final class State implements Serializable {
@@ -164,13 +180,15 @@ private static final class State implements Serializable {
private final boolean reflowLongStrings;
private final boolean reorderImports;
private final boolean formatJavadoc;
+ private final boolean cleanup;
State(String version,
String style,
JarState jarState,
boolean reflowLongStrings,
boolean reorderImports,
- boolean formatJavadoc) {
+ boolean formatJavadoc,
+ boolean cleanup) {
JVM_SUPPORT.assertFormatterSupported(version);
ModuleHelper.doOpenInternalPackagesIfRequired();
this.jarState = jarState;
@@ -179,13 +197,14 @@ private static final class State implements Serializable {
this.reflowLongStrings = reflowLongStrings;
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
+ this.cleanup = cleanup;
}
FormatterFunc createFormat() throws Exception {
final ClassLoader classLoader = jarState.getClassLoader();
Class> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.java.GoogleJavaFormatFormatterFunc");
- Constructor> constructor = formatterFunc.getConstructor(String.class, String.class, boolean.class, boolean.class, boolean.class);
- FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc);
+ Constructor> constructor = formatterFunc.getConstructor(String.class, String.class, boolean.class, boolean.class, boolean.class, boolean.class);
+ FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc, cleanup);
return JVM_SUPPORT.suggestLaterVersionOnError(version, googleJavaFormatFormatterFunc);
}
diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md
index d3249bf262..ea9fa8cd41 100644
--- a/plugin-gradle/CHANGES.md
+++ b/plugin-gradle/CHANGES.md
@@ -3,6 +3,11 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).
## [Unreleased]
+### Added
+- Add `cleanup(false)` to `googleJavaFormat()` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
+### Changes
+- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))
+ This version applies some small non-whitespace cleanups by default (removing redundant semicolons, empty annotation parentheses, and parentheses around single lambda parameters), which can be disabled with `cleanup(false)`.
## [8.10.4] - 2026-10-08
diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md
index f6e9570853..023f78fb2a 100644
--- a/plugin-gradle/README.md
+++ b/plugin-gradle/README.md
@@ -380,8 +380,9 @@ spotless {
googleJavaFormat()
// optional: you can specify a specific version (>= 1.8) and/or switch to AOSP style
// and/or reflow long strings
+ // and/or disable non-whitespace cleanups (google-java-format >= 2.0.0)
// and/or use custom group artifact (you probably don't need this)
- googleJavaFormat('1.8').aosp().reflowLongStrings().formatJavadoc(false).reorderImports(false).groupArtifact('com.google.googlejavaformat:google-java-format')
+ googleJavaFormat('1.8').aosp().reflowLongStrings().formatJavadoc(false).reorderImports(false).cleanup(false).groupArtifact('com.google.googlejavaformat:google-java-format')
```
### palantir-java-format
diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java
index 9411811322..33d9dbac3a 100644
--- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java
+++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java
@@ -211,6 +211,7 @@ public class GoogleJavaFormatConfig {
boolean reflowLongStrings;
boolean reorderImports;
boolean formatJavadoc = true;
+ boolean cleanup = true;
GoogleJavaFormatConfig(String version) {
this.version = Objects.requireNonNull(version);
@@ -261,6 +262,13 @@ public GoogleJavaFormatConfig formatJavadoc(boolean formatJavadoc) {
return this;
}
+ /** Enables or disables google-java-format's non-whitespace cleanups (google-java-format 2.0.0 and later), which are enabled by default. */
+ public GoogleJavaFormatConfig cleanup(boolean cleanup) {
+ this.cleanup = cleanup;
+ replaceStep(createStep());
+ return this;
+ }
+
private FormatterStep createStep() {
return GoogleJavaFormatStep.create(
groupArtifact,
@@ -269,7 +277,8 @@ private FormatterStep createStep() {
provisioner(),
reflowLongStrings,
reorderImports,
- formatJavadoc);
+ formatJavadoc,
+ cleanup);
}
}
diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md
index 46c259e6e9..59b4ceadbf 100644
--- a/plugin-maven/CHANGES.md
+++ b/plugin-maven/CHANGES.md
@@ -3,6 +3,11 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
## [Unreleased]
+### Added
+- Add `false` to `` to disable google-java-format's non-whitespace cleanups. ([#3157](https://github.com/diffplug/spotless/pull/3157))
+### Changes
+- Bump default `google-java-format` version `1.36.1` -> `2.0.0`. ([#3157](https://github.com/diffplug/spotless/pull/3157))
+ This version applies some small non-whitespace cleanups by default (removing redundant semicolons, empty annotation parentheses, and parentheses around single lambda parameters), which can be disabled with `false`.
## [3.10.4] - 2026-10-08
diff --git a/plugin-maven/README.md b/plugin-maven/README.md
index 5c57f5eca9..ea7c4c72cf 100644
--- a/plugin-maven/README.md
+++ b/plugin-maven/README.md
@@ -345,6 +345,7 @@ Types in `java.lang` and in the file's own package are shortened without adding
true
false
false.
+ false
com.google.googlejavaformat:google-java-format
diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java
index d0a2b24954..e86393f529 100644
--- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java
+++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2023 DiffPlug
+ * Copyright 2016-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,9 @@ public class GoogleJavaFormat implements FormatterStepFactory {
@Parameter
private Boolean formatJavadoc;
+ @Parameter
+ private Boolean cleanup;
+
@Override
public FormatterStep newFormatterStep(FormatterStepConfig config) {
String groupArtifact = this.groupArtifact != null ? this.groupArtifact : GoogleJavaFormatStep.defaultGroupArtifact();
@@ -49,6 +52,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) {
boolean reflowLongStrings = this.reflowLongStrings != null ? this.reflowLongStrings : GoogleJavaFormatStep.defaultReflowLongStrings();
boolean reorderImports = this.reorderImports != null ? this.reorderImports : GoogleJavaFormatStep.defaultReorderImports();
boolean formatJavadoc = this.formatJavadoc != null ? this.formatJavadoc : GoogleJavaFormatStep.defaultFormatJavadoc();
- return GoogleJavaFormatStep.create(groupArtifact, version, style, config.getProvisioner(), reflowLongStrings, reorderImports, formatJavadoc);
+ boolean cleanup = this.cleanup != null ? this.cleanup : GoogleJavaFormatStep.defaultCleanup();
+ return GoogleJavaFormatStep.create(groupArtifact, version, style, config.getProvisioner(), reflowLongStrings, reorderImports, formatJavadoc, cleanup);
}
}
diff --git a/testlib/src/main/resources/java/googlejavaformat/TextBlockGjf2.clean b/testlib/src/main/resources/java/googlejavaformat/TextBlockGjf2.clean
new file mode 100644
index 0000000000..cf0dc428e0
--- /dev/null
+++ b/testlib/src/main/resources/java/googlejavaformat/TextBlockGjf2.clean
@@ -0,0 +1,15 @@
+import mylib.UsedA;
+import mylib.UsedB;
+
+public class Java {
+ public static void main(String[] args) {
+ var a =
+ """
+ Howdy
+ Partner!
+ """;
+ System.out.println(a);
+ UsedB.someMethod();
+ UsedA.someMethod();
+ }
+}
diff --git a/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java
index cae19f4bca..5d42ea0d4b 100644
--- a/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java
+++ b/testlib/src/test/java/com/diffplug/spotless/java/GoogleJavaFormatStepTest.java
@@ -34,7 +34,8 @@ class GoogleJavaFormatStepTest extends ResourceHarness {
@Test
void jvm13Features() throws Exception {
try (StepHarness step = StepHarness.forStep(GoogleJavaFormatStep.create(TestProvisioner.mavenCentral()))) {
- step.testResource("java/googlejavaformat/TextBlock.dirty", "java/googlejavaformat/TextBlock.clean");
+ // google-java-format 2.0.0 indents text block contents even when long strings aren't reflowed
+ step.testResource("java/googlejavaformat/TextBlock.dirty", defaultVersionIsAtLeast2() ? "java/googlejavaformat/TextBlockGjf2.clean" : "java/googlejavaformat/TextBlock.clean");
}
}
@@ -117,6 +118,29 @@ void behaviorWithSkipFormatJavadoc() throws Exception {
}
}
+ @Test
+ void behaviorWithCleanup() throws Exception {
+ String unformatted = "class T {\n @Deprecated() void f() { g((x) -> x);; }\n}\n";
+ String withoutCleanups = "class T {\n @Deprecated()\n void f() {\n g((x) -> x);\n ;\n }\n}\n";
+ // google-java-format versions before 2.0.0 don't have cleanups
+ String withCleanups = defaultVersionIsAtLeast2() ? "class T {\n @Deprecated\n void f() {\n g(x -> x);\n }\n}\n" : withoutCleanups;
+ try (StepHarness step = StepHarness.forStep(createWithCleanup(true))) {
+ step.test(unformatted, withCleanups);
+ }
+ try (StepHarness step = StepHarness.forStep(createWithCleanup(false))) {
+ step.test(unformatted, withoutCleanups);
+ }
+ }
+
+ /** The default version depends on the JVM, e.g. google-java-format 2.0.0 requires Java 21. */
+ private static boolean defaultVersionIsAtLeast2() {
+ return !GoogleJavaFormatStep.defaultVersion().startsWith("1.");
+ }
+
+ private static FormatterStep createWithCleanup(boolean cleanup) {
+ return GoogleJavaFormatStep.create(GoogleJavaFormatStep.defaultGroupArtifact(), GoogleJavaFormatStep.defaultVersion(), GoogleJavaFormatStep.defaultStyle(), TestProvisioner.mavenCentral(), GoogleJavaFormatStep.defaultReflowLongStrings(), GoogleJavaFormatStep.defaultReorderImports(), GoogleJavaFormatStep.defaultFormatJavadoc(), cleanup);
+ }
+
@Test
void behaviorWithCustomGroupArtifact() throws Exception {
FormatterStep step = GoogleJavaFormatStep.create(GoogleJavaFormatStep.defaultGroupArtifact(), GoogleJavaFormatStep.defaultVersion(), GoogleJavaFormatStep.defaultStyle(), TestProvisioner.mavenCentral(), false);