Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]

### Features
- `googleJavaFormat` gained a `maxLineLength` option, which overrides the 100-column limit prescribed by the GOOGLE and AOSP styles. It requires `google-java-format` `1.37.0` or later, and the step now also works at all with `1.37.0+` (upstream changed `JavaFormatterOptions.Style` from an enum to a record in `1.37.0`). ([#3151](https://github.com/diffplug/spotless/issues/3151))

## [4.10.4] - 2026-10-08

### Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,6 +15,8 @@
*/
package com.diffplug.spotless.glue.java;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -46,19 +48,65 @@ 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, int maxLineLength) {
this.version = Objects.requireNonNull(version);
this.formatterStyle = Style.valueOf(Objects.requireNonNull(style));
this.formatterStyle = toStyle(style);
this.reflowStrings = reflowStrings;
this.reorderImports = reorderImports;

JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder().style(formatterStyle);
Style effectiveStyle = formatterStyle;
if (maxLineLength > 0) {
effectiveStyle = withMaxLineLength(formatterStyle, maxLineLength, version);
}
JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder().style(effectiveStyle);
if (!formatJavadoc) {
builder = builder.formatJavadoc(false);
}
this.formatter = new Formatter(builder.build());
}

/**
* Parses a style name without calling {@code Style.valueOf}, which existed only while
* {@code Style} was an enum (google-java-format {@literal <=} 1.36); since 1.37 it is a record,
* but the {@code GOOGLE} and {@code AOSP} constants are present in both.
*/
@Nonnull
private static Style toStyle(@Nonnull String style) {
switch (Objects.requireNonNull(style)) {
case "GOOGLE":
return Style.GOOGLE;
case "AOSP":
return Style.AOSP;
default:
throw new IllegalArgumentException("style must be GOOGLE or AOSP, but was: " + style);
}
}

/**
* Sets the column limit on the style's builder. That builder exists only in google-java-format
* 1.37.0+, while this glue code is compiled against 1.28.0 so that it keeps working on older
* versions, hence the reflection (https://github.com/diffplug/spotless/issues/3151).
* Methods are looked up on the public {@code Style$Builder} type rather than on the runtime
* class of the builder, because the runtime class generated by AutoBuilder is package-private.
*/
@Nonnull
private static Style withMaxLineLength(@Nonnull Style style, int maxLineLength, @Nonnull String version) {
try {
Method createBuilder = Style.class.getMethod("builder");
Class<?> builderType = createBuilder.getReturnType();
Object builder = createBuilder.invoke(null);
builder = builderType.getMethod(style == Style.AOSP ? "aosp" : "google").invoke(builder);
builder = builderType.getMethod("maxLineLength", int.class).invoke(builder, maxLineLength);
return (Style) builderType.getMethod("build").invoke(builder);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"maxLineLength requires google-java-format 1.37.0 or later (you are using " + version + ")", e);
} catch (IllegalAccessException | InvocationTargetException e) {
Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
throw new IllegalArgumentException("Failed to apply maxLineLength " + maxLineLength, cause);
}
}

@Override
@Nonnull
public String apply(@Nonnull String input) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
Expand All @@ -34,6 +35,10 @@ 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;
/** 0 means "not set", i.e. use whatever column limit the formatter's style prescribes. */
private static final int DEFAULT_MAX_LINE_LENGTH = 0;
/** The first version which made `JavaFormatterOptions.Style` configurable, see [#3151](https://github.com/diffplug/spotless/issues/3151). */
static final String MIN_VERSION_FOR_MAX_LINE_LENGTH = "1.37.0";
private static final String NAME = "google-java-format";
private static final String MAVEN_COORDINATE = "com.google.googlejavaformat:google-java-format";

Expand All @@ -44,19 +49,22 @@ public final class GoogleJavaFormatStep implements Serializable {
private final boolean reflowLongStrings;
private final boolean reorderImports;
private final boolean formatJavadoc;
private final int maxLineLength;

private GoogleJavaFormatStep(JarState.Promised jarState,
String version,
String style,
boolean reflowLongStrings,
boolean reorderImports,
boolean formatJavadoc) {
boolean formatJavadoc,
int maxLineLength) {
this.jarState = jarState;
this.version = version;
this.style = style;
this.reflowLongStrings = reflowLongStrings;
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
this.maxLineLength = maxLineLength;
}

/** Creates a step which formats everything - code, import order, and unused imports. */
Expand Down Expand Up @@ -89,23 +97,37 @@ 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_MAX_LINE_LENGTH);
}

/**
* Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings.
* A positive {@code maxLineLength} overrides the column limit prescribed by the style, and requires google-java-format {@code 1.37.0} or later.
*/
public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc, int maxLineLength) {
return createInternally(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc, maxLineLength, 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(), DEFAULT_MAX_LINE_LENGTH, 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, int maxLineLength, 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'");
}
Objects.requireNonNull(version, "version");
Objects.requireNonNull(style, "style");
Objects.requireNonNull(provisioner, "provisioner");
if (maxLineLength < 0) {
throw new IllegalArgumentException("maxLineLength must be positive, but was: " + maxLineLength);
}
if (maxLineLength > 0 && compareVersions(version, MIN_VERSION_FOR_MAX_LINE_LENGTH) < 0) {
throw new IllegalArgumentException("maxLineLength requires google-java-format " + MIN_VERSION_FOR_MAX_LINE_LENGTH + " or later (you are using " + version + ")");
}

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, maxLineLength);
if (removeImports) {
return FormatterStep.create(name,
step,
Expand Down Expand Up @@ -150,8 +172,36 @@ public static boolean defaultFormatJavadoc() {
return DEFAULT_FORMAT_JAVADOC;
}

public static int defaultMaxLineLength() {
return DEFAULT_MAX_LINE_LENGTH;
}

/** Compares dotted numeric versions, e.g. {@code compareVersions("1.36.1", "1.37.0") < 0}. */
private static int compareVersions(String left, String right) {
int[] lhs = parseVersion(left);
int[] rhs = parseVersion(right);
int numberOfElements = Math.max(lhs.length, rhs.length);
lhs = Arrays.copyOf(lhs, numberOfElements);
rhs = Arrays.copyOf(rhs, numberOfElements);
for (int i = 0; i < numberOfElements; i++) {
if (lhs[i] != rhs[i]) {
return lhs[i] > rhs[i] ? 1 : -1;
}
}
return 0;
}

private static int[] parseVersion(String version) {
try {
String withoutSuffix = version.endsWith("-SNAPSHOT") ? version.substring(0, version.length() - "-SNAPSHOT".length()) : version;
return Arrays.stream(withoutSuffix.split("\\.")).mapToInt(Integer::parseInt).toArray();
} catch (Exception e) {
throw new IllegalArgumentException("Not a semantic version: " + version, e);
}
}

private State equalityState() {
return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc);
return new State(version, style, jarState.get(), reflowLongStrings, reorderImports, formatJavadoc, maxLineLength);
}

private static final class State implements Serializable {
Expand All @@ -164,13 +214,15 @@ private static final class State implements Serializable {
private final boolean reflowLongStrings;
private final boolean reorderImports;
private final boolean formatJavadoc;
private final int maxLineLength;

State(String version,
String style,
JarState jarState,
boolean reflowLongStrings,
boolean reorderImports,
boolean formatJavadoc) {
boolean formatJavadoc,
int maxLineLength) {
JVM_SUPPORT.assertFormatterSupported(version);
ModuleHelper.doOpenInternalPackagesIfRequired();
this.jarState = jarState;
Expand All @@ -179,13 +231,14 @@ private static final class State implements Serializable {
this.reflowLongStrings = reflowLongStrings;
this.reorderImports = reorderImports;
this.formatJavadoc = formatJavadoc;
this.maxLineLength = maxLineLength;
}

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, int.class);
FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc, maxLineLength);

return JVM_SUPPORT.suggestLaterVersionOnError(version, googleJavaFormatFormatterFunc);
}
Expand Down
3 changes: 3 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]

### Features
- `googleJavaFormat` gained `maxLineLength(int)`, which overrides the 100-column limit prescribed by the GOOGLE and AOSP styles. It requires `google-java-format` `1.37.0` or later. ([#3151](https://github.com/diffplug/spotless/issues/3151))

## [8.10.4] - 2026-10-08

### Changes
Expand Down
3 changes: 2 additions & 1 deletion plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 override the 100-column limit (requires google-java-format 1.37.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().maxLineLength(120).formatJavadoc(false).reorderImports(false).groupArtifact('com.google.googlejavaformat:google-java-format')
```

### palantir-java-format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ public class GoogleJavaFormatConfig {
boolean reflowLongStrings;
boolean reorderImports;
boolean formatJavadoc = true;
int maxLineLength = GoogleJavaFormatStep.defaultMaxLineLength();

GoogleJavaFormatConfig(String version) {
this.version = Objects.requireNonNull(version);
Expand Down Expand Up @@ -261,6 +262,16 @@ public GoogleJavaFormatConfig formatJavadoc(boolean formatJavadoc) {
return this;
}

/**
* Overrides the column limit prescribed by the style (100 for both GOOGLE and AOSP).
* Requires google-java-format 1.37.0 or later.
*/
public GoogleJavaFormatConfig maxLineLength(int maxLineLength) {
this.maxLineLength = maxLineLength;
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return GoogleJavaFormatStep.create(
groupArtifact,
Expand All @@ -269,7 +280,8 @@ private FormatterStep createStep() {
provisioner(),
reflowLongStrings,
reorderImports,
formatJavadoc);
formatJavadoc,
maxLineLength);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.diffplug.gradle.spotless;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.IOException;

import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -101,4 +103,28 @@ void integrationWithSkipJavadocFormatting() throws IOException {
"googleJavaFormat()");
checkRunsThenUpToDate();
}

@Test
void integrationWithMaxLineLength() throws IOException {
assumeTrue(Jvm.version() >= 21, "google-java-format 1.37.0 requires JVM 21+");

setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java {",
" target file('test.java')",
" googleJavaFormat('1.37.0').maxLineLength(240)",
" }",
"}");

setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test");
gradleRunner().withArguments("spotlessApply").build();
assertFile("test.java").sameAsResource("java/googlejavaformat/JavaCodeFormattedMaxLineLength240.test");

checkRunsThenUpToDate();
}
}
3 changes: 3 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]

### Features
- `<googleJavaFormat>` gained a `<maxLineLength>` parameter, which overrides the 100-column limit prescribed by the GOOGLE and AOSP styles. It requires `google-java-format` `1.37.0` or later. ([#3151](https://github.com/diffplug/spotless/issues/3151))

## [3.10.4] - 2026-10-08

### Changes
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ Types in `java.lang` and in the file's own package are shortened without adding
<version>1.8</version> <!-- optional, 1.8 is the minimum supported version for Java 11 -->
<style>GOOGLE</style> <!-- or AOSP (optional) -->
<reflowLongStrings>true</reflowLongStrings> <!-- optional -->
<maxLineLength>120</maxLineLength> <!-- optional, requires google-java-format 1.37.0+ -->
<formatJavadoc>false</formatJavadoc> <!-- optional -->
<reorderImports>false</reorderImports>. <!-- optional -->
<!-- optional: custom group artifact (you probably don't need this) -->
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -41,6 +41,9 @@ public class GoogleJavaFormat implements FormatterStepFactory {
@Parameter
private Boolean formatJavadoc;

@Parameter
private Integer maxLineLength;

@Override
public FormatterStep newFormatterStep(FormatterStepConfig config) {
String groupArtifact = this.groupArtifact != null ? this.groupArtifact : GoogleJavaFormatStep.defaultGroupArtifact();
Expand All @@ -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);
int maxLineLength = this.maxLineLength != null ? this.maxLineLength : GoogleJavaFormatStep.defaultMaxLineLength();
return GoogleJavaFormatStep.create(groupArtifact, version, style, config.getProvisioner(), reflowLongStrings, reorderImports, formatJavadoc, maxLineLength);
}
}
Loading