UpdateGradleWrapper emits malformed CLASSPATH="\\\"\\\"" for Gradle 8.14.x
What version of OpenRewrite are you using?
openrewrite/rewrite - current main branch (confirmed in GradleWrapperScriptDownloader.java, SHA eb4dabd)
- Affects all consumers of the pre-computed wrapper resources for Gradle 8.14-rc-1 through 9.0-milestone-1
How are you running OpenRewrite?
Via the Moderne platform (which uses the UpdateGradleWrapper recipe internally as part of DevelocityOnboarding and similar recipes). The issue is not in recipe execution - it is in the pre-computed wrapper scripts stored as resources in rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-wrapper/unix/.
The affected stored file is:
rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-wrapper/unix/b67a59d5.txt
This file is used for all Gradle versions from 8.14-rc-1 through 8.14.4 (and equivalently 665958e3.txt covers 9.0.0 through 9.0-milestone-2).
What is the smallest, simplest way to reproduce the problem?
Run UpdateGradleWrapper targeting Gradle 8.14.4 on any project whose current wrapper is pre-8.14. Inspect the resulting gradlew. The CLASSPATH line will be:
For comparison, the same recipe targeting Gradle 9.1.0 produces the correct:
What did you expect to see?
This is what the official Gradle 8.14.4 wrapper template renders when invoked via ./gradlew wrapper --gradle-version 8.14.4, and what OpenRewrite itself correctly emits for Gradle 9.1.0-rc-1 and later.
What did you see instead?
When the shell evaluates this, CLASSPATH is set to the literal string \"\" (backslash-quote-backslash-quote), which is then passed to the JVM as -classpath "\"\"". This is not a valid classpath.
Root cause
The bug is in GradleWrapperScriptDownloader.java in the unixBindings() method.
For Gradle 9.1.0-rc-1+ (correct):
binding.put("classpath", "");
- produces
CLASSPATH= in the script -
For Gradle 8.14-rc-1 through 9.0-milestone-1 (incorrect):
binding.put("classpath", "\"\\\\\\\\\\\"\\\\\\\\\\\"\"");
- produces
CLASSPATH="\\\"\\\"" in the script -
The over-escaped Java string was further compensated by a fragile post-processing patch in renderTemplate():
.replace("CLASSPATH=\"\\\\\\\\\\\"\\\\\\\\\\\"", "CLASSPATH=\"\\\\\\\"\\\\\\\"");
This bandage trims one layer of extra escaping but still leaves CLASSPATH="\\\"\\\"" rather than a clean empty value.
Why is CLASSPATH empty for 8.14+?
Gradle 8.14 changed the wrapper invocation from class-based to JAR-based. The old pattern:
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# ...
eval set -- ... -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain ...
The new pattern (8.14+):
CLASSPATH= # intentionally empty
# ...
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Because -jar is used, the JVM specification states that -classpath is ignored. So CLASSPATH="\\\"\\\"" does not break functionality, but it is confusing to code reviewers and linters, and differs from the official Gradle-generated wrapper.
What is the full stack trace of any errors you encountered?
No runtime error - the wrapper functions correctly because -jar overrides -classpath. However, the change raises reviewer concern and fails shellcheck-style audits.
Suggested fix
In unixBindings(), apply the same fix used for 9.1.0-rc-1+ to the 8.14-rc-1 - 9.0-milestone-1 range:
} else if (current.compareTo(GRADLE_8_14_RC_1) >= 0) {
- binding.put("classpath", "\"\\\\\\\\\\\"\\\\\\\\\\\"\"");
+ binding.put("classpath", "");
binding.put("entryPointArgs", "-jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\"");
binding.put("mainClassName", "");
Also apply the same to 9.0-milestone-2:
} else if (current.compareTo(GRADLE_9_0_M_2) >= 0) {
- binding.put("classpath", "\"\\\\\\\"\\\\\\\"\"");
+ binding.put("classpath", "");
And remove the compensating .replace() in renderTemplate() once the bindings are fixed:
- .replace("CLASSPATH=\"\\\\\\\\\\\"\\\\\\\\\\\"", "CLASSPATH=\"\\\\\\\"\\\\\\\"");
After the fix, the pre-computed resource files for all affected Gradle versions should be regenerated by re-running GradleWrapperScriptDownloader.main().
Are you interested in contributing a fix to OpenRewrite?
Happy to submit a PR with the above fix if the OpenRewrite team confirms this approach.
UpdateGradleWrapperemits malformedCLASSPATH="\\\"\\\""for Gradle 8.14.xWhat version of OpenRewrite are you using?
openrewrite/rewrite- currentmainbranch (confirmed inGradleWrapperScriptDownloader.java, SHAeb4dabd)How are you running OpenRewrite?
Via the Moderne platform (which uses the
UpdateGradleWrapperrecipe internally as part ofDevelocityOnboardingand similar recipes). The issue is not in recipe execution - it is in the pre-computed wrapper scripts stored as resources inrewrite-gradle/src/main/resources/META-INF/rewrite/gradle-wrapper/unix/.The affected stored file is:
This file is used for all Gradle versions from
8.14-rc-1through8.14.4(and equivalently665958e3.txtcovers9.0.0through9.0-milestone-2).What is the smallest, simplest way to reproduce the problem?
Run
UpdateGradleWrappertargeting Gradle8.14.4on any project whose current wrapper is pre-8.14. Inspect the resultinggradlew. TheCLASSPATHline will be:CLASSPATH="\\\"\\\""For comparison, the same recipe targeting Gradle
9.1.0produces the correct:What did you expect to see?
This is what the official Gradle 8.14.4 wrapper template renders when invoked via
./gradlew wrapper --gradle-version 8.14.4, and what OpenRewrite itself correctly emits for Gradle 9.1.0-rc-1 and later.What did you see instead?
CLASSPATH="\\\"\\\""When the shell evaluates this,
CLASSPATHis set to the literal string\"\"(backslash-quote-backslash-quote), which is then passed to the JVM as-classpath "\"\"". This is not a valid classpath.Root cause
The bug is in
GradleWrapperScriptDownloader.javain theunixBindings()method.For Gradle 9.1.0-rc-1+ (correct):
CLASSPATH=in the script -For Gradle 8.14-rc-1 through 9.0-milestone-1 (incorrect):
CLASSPATH="\\\"\\\""in the script -The over-escaped Java string was further compensated by a fragile post-processing patch in
renderTemplate():This bandage trims one layer of extra escaping but still leaves
CLASSPATH="\\\"\\\""rather than a clean empty value.Why is CLASSPATH empty for 8.14+?
Gradle 8.14 changed the wrapper invocation from class-based to JAR-based. The old pattern:
The new pattern (8.14+):
Because
-jaris used, the JVM specification states that-classpathis ignored. SoCLASSPATH="\\\"\\\""does not break functionality, but it is confusing to code reviewers and linters, and differs from the official Gradle-generated wrapper.What is the full stack trace of any errors you encountered?
No runtime error - the wrapper functions correctly because
-jaroverrides-classpath. However, the change raises reviewer concern and fails shellcheck-style audits.Suggested fix
In
unixBindings(), apply the same fix used for 9.1.0-rc-1+ to the 8.14-rc-1 - 9.0-milestone-1 range:Also apply the same to
9.0-milestone-2:And remove the compensating
.replace()inrenderTemplate()once the bindings are fixed:After the fix, the pre-computed resource files for all affected Gradle versions should be regenerated by re-running
GradleWrapperScriptDownloader.main().Are you interested in contributing a fix to OpenRewrite?
Happy to submit a PR with the above fix if the OpenRewrite team confirms this approach.