Regression in 8.6.0: groovyGradle/greclipse fails with "roundtripStateInternal or equalityStateInternal should be non-null" during input fingerprinting
- Dominant language
- Java
- Stars
- 5.6k
- Forks
- 559
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 34
Description
## Summary
Starting with **Spotless 8.6.0**, the `groovyGradle { greclipse() }` task fails with:
```
If the initializer was null, then one of roundtripStateInternal or equalityStateInternal should be non-null, and neither was
```
This is a **regression**: **8.4.0 and 8.5.0 work** with the exact same project/config/Gradle version; **8.6.0 fails**. Pinning back to 8.5.0 is our current workaround.
It is closely related to (and may be the same root cause as) #2927 and #2502, but we can pinpoint it to the 8.5.0 → 8.6.0 bump, and it does **not** require the configuration cache (it happens with config cache disabled / not configured).
## Environment
- Spotless Gradle plugin **8.6.0** (8.4.0 and 8.5.0 OK)
- Gradle **9.5.1**
- JDK 26 (Temurin) — also reproduces on the same setup regardless
- Formatter: `groovyGradle { greclipse() }` (default greclipse → Groovy-Eclipse `5.7.0`/`e4.35`, Groovy `5.0.0`)
- Configuration cache: **not enabled**
- Build cache: disabled on the command line (`--no-build-cache`) — does **not** affect the outcome
## Spotless config (relevant part)
```groovy
spotless {
groovyGradle {
toggleOffOn()
greclipse()
leadingTabsToSpaces()
}
}
```
## How to reproduce
Run on a **clean Gradle user home** (e.g. a fresh CI container where greclipse is provisioned from scratch):
```
./gradlew spotlessCheck --no-daemon --no-build-cache --stacktrace
```
`:spotlessGroovyGradle` fails. It does **not** reproduce against a warm local cache where greclipse is already provisioned — which suggests the failure is tied to the freshly-built greclipse step state rather than to any formatting violation.
## Stack trace
The exception is thrown while Gradle **fingerprints the task's input properties** (before execution), when it calls `hashCode()` on Spotless's `ConfigurationCacheHackList`:
```
Caused by: java.lang.IllegalStateException: If the initializer was null, then one of roundtripStateInternal or equalityStateInternal should be non-null, and neither was
at com.diffplug.spotless.FormatterStepSerializationRoundtrip.writeObject(FormatterStepSerializationRoundtrip.java:74)
at com.diffplug.spotless.FormatterStepSerializationRoundtrip$HackClone.writeObject(FormatterStepSerializationRoundtrip.java:121)
at com.diffplug.spotless.ConfigurationCacheHackList.writeObject(ConfigurationCacheHackList.java:78)
at com.diffplug.spotless.LazyForwardingEquality.toBytes(LazyForwardingEquality.java:110)
at com.diffplug.spotless.FormatterStepEqualityOnStateSerialization.lambda$serializedState$0(FormatterStepEqualityOnStateSerialization.java:95)
at com.diffplug.spotless.ThrowingEx.get(ThrowingEx.java:67)
at com.diffplug.spotless.FormatterStepEqualityOnStateSerialization.serializedState(FormatterStepEqualityOnStateSerialization.java:95)
at com.diffplug.spotless.FormatterStepEqualityOnStateSerialization.hashCode(FormatterStepEqualityOnStateSerialization.java:75)
at com.diffplug.spotless.FormatterStepSerializationRoundtrip$HackClone.hashCode(FormatterStepSerializationRoundtrip.java:142)
at com.diffplug.spotless.ConfigurationCacheHackList.hashCode(ConfigurationCacheHackList.java:151)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputProperty(DefaultInputFingerprinter.java:131)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitMutableInputs(TaskExecution.java:336)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:77)
at org.gradle.internal.execution.steps.CaptureMutableStateBeforeExecutionStep.captureExecutionStateWithOutputs(CaptureMutableStateBeforeExecutionStep.java:124)
...
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.executeMutable(LoadPreviousExecutionStateStep.java:36)
```
## Analysis / likely cause
- The failure is in `FormatterStepSerializationRoundtrip.writeObject` (line 74), which asserts that if `initializer == null` then one of `roundtripStateInternal` / `equalityStateInternal` must be non-null — and here **both are null**.
- It is reached via `ConfigurationCacheHackList.hashCode()` during Gradle's **input fingerprinting** (`DefaultInputFingerprinter` / `CaptureMutableStateBeforeExecutionStep`), i.e. before the task action runs — so it triggers for ordinary up-to-date checking, independent of the build cache and without the configuration cache.
- Because it only reproduces with a **freshly provisioned greclipse** (clean Gradle user home) and not against a warm cache, the greclipse step's state appears to be left without a computed `roundtripStateInternal`/`equalityStateInternal` (initializer already consumed) at the point Gradle serializes it for the fingerprint.
## Workaround
Pin the Spotless Gradle plugin to **8.5.0**.
Happy to provide a minimal reproducer repo or extra logs (`--info`/`--debug`) if helpful.
Contributor guide
Assessment
This issue has not been assessed yet.