You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/09/24 08:54:42 UTC
(grails-core) 01/03: Upgrade Groovy to 6.0.0
This is an automated email from the ASF dual-hosted git repository.
matrei pushed a commit to branch deps/groovy-6.0.0
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit d1cbf7fd0a822dd4791cc5869d4af594705e210a
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Wed Sep 23 22:57:03 2026 +0200
Upgrade Groovy to 6.0.0
Groovy 6 ships the classic (non-invokedynamic) call-site runtime as the
separate groovy-callsite module, and its compiler refuses to generate that
bytecode without it on the compile classpath. Grails 8 compiles with indy
off, so grails-common exports the module and the root build adds it to
every source set of the modules below grails-common.
Spock has no groovy-6.0 build, and 2.4-groovy-5.0 checks the Groovy major
both in its compiler AST transform and when a Spec starts. The opt-out
system property is set on the forked Groovy compiler and Test tasks by
CompilePlugin, on groovydoc runs, in the end-to-end build, and for
applications by the Grails Gradle plugin on GroovyCompile, Test and the
forked view compiler. A build that sets its own value keeps it.
Groovy 6 moves groovysh to JLine 4, so jline.version follows and the
SBOM plugin corrects the JLine 4 licence metadata by pattern.
Source changes for Groovy 6.0.0: parameterized types and type variables
are no longer class literals; `!x instanceof T` is rejected (it was always
false); a statically compiled map-style constructor call needs an
explicit @MapConstructor; @Delegate generates over declared overrides
(config write filtering, dirty-checking reversed views) and no longer
satisfies the abstract-method check for the JSON view helper's render
overloads; the stub generator writes a precompiled trait's static
methods as `static abstract`; and generic trait methods are specialized
on the implementing class. Each workaround is commented with its
trigger, failure, deletion condition and impact class as catalogued in
issue #16157 on the 9.0.x line.
The upgrade guide gains a Groovy 6 section covering the call-site module,
the Spock opt-out, JLine 4 and the application-source changes.
---
.agents/skills/gradle-developer/SKILL.md | 8 ++--
.agents/skills/grails-developer/SKILL.md | 4 +-
.agents/skills/groovy-developer/SKILL.md | 6 +--
AGENTS.md | 6 +--
.../apache/grails/buildsrc/CompilePlugin.groovy | 27 +++++++++---
.../apache/grails/buildsrc/GroovydocRunner.groovy | 4 ++
.../org/apache/grails/buildsrc/SbomPlugin.groovy | 33 +++++++-------
.../grails/buildsrc/CompilePluginSpec.groovy | 11 +++++
.../apache/grails/buildsrc/SbomPluginSpec.groovy | 28 ++++++++++++
build.gradle | 17 ++++++++
dependencies.gradle | 12 ++++-
end-to-end/README.md | 4 +-
end-to-end/build.gradle | 11 +++++
end-to-end/legacy-g7-command-plugin/build.gradle | 2 +-
end-to-end/settings.gradle | 2 +-
gradle/mongodb-forked-test-config.gradle | 4 +-
gradle/mongodb-test-config.gradle | 4 +-
grails-common/build.gradle | 7 +++
.../main/groovy/grails/boot/GrailsBanner.groovy | 3 +-
.../config/external/WriterFilteringMap.groovy | 5 ++-
.../orm/hibernate/HibernateGormInstanceApi.groovy | 4 +-
.../hibernate/HibernateGormInstanceApiSpec.groovy | 19 ++++++++
.../compiler/gorm/GormEntityTransformSpec.groovy | 22 +++++++++-
.../dirty/checking/DirtyCheckingList.groovy | 5 ++-
.../dirty/checking/DirtyCheckingSortedSet.groovy | 3 +-
.../src/en/guide/upgrading/upgrading80x.adoc | 51 +++++++++++++++++++++-
.../formfields/BeanPropertyAccessorImpl.groovy | 6 +++
.../formfields/BeanPropertyAccessorImplSpec.groovy | 51 ++++++++++++++++++++++
.../gradle/plugin/core/GrailsGradlePlugin.groovy | 17 ++++++++
.../views/AbstractGroovyTemplateCompileTask.groovy | 11 +++++
.../core/GrailsGradlePluginToolchainSpec.groovy | 9 ++++
.../fork-settings-defaults/build.gradle | 19 ++++++++
.../Sitemesh3RenderViewMutatorSpec.groovy | 2 +-
.../oauth2/SpringSecurityOauth2BaseService.groovy | 3 +-
.../testing/ParameterizedGrailsUnitTest.groovy | 3 +-
.../mongo/StartMongoGrailsUnitExtension.groovy | 3 +-
.../grails/validation/ValidateableTraitSpec.groovy | 13 ++++++
.../json/view/api/GrailsJsonViewHelper.groovy | 26 ++++++++---
.../json/view/api/internal/TemplateRenderer.groovy | 35 +++++++++++++++
.../plugin/json/view/TemplateRendererSpec.groovy | 23 ++++++++++
40 files changed, 466 insertions(+), 57 deletions(-)
diff --git a/.agents/skills/gradle-developer/SKILL.md b/.agents/skills/gradle-developer/SKILL.md
index 98a092170c..2c7196f5d4 100644
--- a/.agents/skills/gradle-developer/SKILL.md
+++ b/.agents/skills/gradle-developer/SKILL.md
@@ -138,10 +138,10 @@ Presence-based flags (property **present**, value optional) match `skipFunctiona
Current line: **Gradle 9.7.1** (`distributionUrl` + `gradleToolingApiVersion=9.7.1`). Upstream may already ship a newer 9.7.x patch - this repo rides close to latest **only after** a deliberate multi-location bump PR. Do not "helpfully" jump one wrapper ahead of the rest.
-**Two Groovy stacks:** Gradle itself embeds **Groovy 4** for build logic. Application/runtime code on 8.0.x is **Groovy 5**. That is why `dependencies.gradle` keeps separate maps:
+**Two Groovy stacks:** Gradle itself embeds **Groovy 4** for build logic. Application/runtime code on 8.0.x is **Groovy 6**. That is why `dependencies.gradle` keeps separate maps:
- `gradleBomDependencyVersions` / `gradle-groovy.version` / `gradle-spock.version` → build tooling (Groovy 4 / Spock groovy-4)
-- `bomDependencyVersions` / `groovy.version` / `spock.version` → apps and framework modules (Groovy 5 / Spock groovy-5.0)
+- `bomDependencyVersions` / `groovy.version` / `spock.version` → apps and framework modules (Groovy 6 / Spock groovy-5.0, which runs on Groovy 6 with its version check switched off until Spock ships a groovy-6.0 build)
Never unify those casually.
@@ -293,7 +293,7 @@ For POM property generation, **map key must be the dependency name prefix**:
```groovy
bomDependencyVersions = [
- 'groovy.version': '5.1.3',
+ 'groovy.version': '6.0.0',
]
bomDependencies = [
'groovy': "org.apache.groovy:groovy:${bomDependencyVersions['groovy.version']}",
@@ -596,7 +596,7 @@ Develocity: `https://develocity.apache.org` - build scans publish when authentic
| `includeBuild` the root into `end-to-end` | Publish to `build/local-maven` via `publishAllPublicationsToTestCaseMavenRepoRepository` |
| Use `publishAllToMavenLocal` to feed end-to-end | That fills `~/.m2`; end-to-end reads `<repo>/build/local-maven` |
| Bump `legacy-g7-command-plugin` wrapper to Gradle 9 | Leave it on the Grails 7-pinned Gradle 8.x |
-| Mix Gradle-embedded Groovy 4 pins into app Groovy 5 BOM | Keep `gradleBom*` vs `bom*` maps separate |
+| Mix Gradle-embedded Groovy 4 pins into app Groovy 6 BOM | Keep `gradleBom*` vs `bom*` maps separate |
---
diff --git a/.agents/skills/grails-developer/SKILL.md b/.agents/skills/grails-developer/SKILL.md
index 8e83754866..ddc980310b 100644
--- a/.agents/skills/grails-developer/SKILL.md
+++ b/.agents/skills/grails-developer/SKILL.md
@@ -33,7 +33,7 @@ Activate this skill when developing with current Grails, including:
Current Grails is built on:
- **Spring Boot**: 4.1.x
- **Spring Framework**: 7.0.x
-- **Groovy**: 5.1.x
+- **Groovy**: 6.0.x
- **Gradle**: 9.6.x
- **Spock**: 2.4-groovy-5.0
- **Jakarta EE**: 10 (migrated from javax.*)
@@ -865,6 +865,6 @@ grails.plugin.springsecurity.controllerAnnotations.staticRules = [
- **Grails User Guide**: https://grails.apache.org/docs/latest/guide/single.html
- **GORM Documentation**: https://grails.apache.org/docs/latest/grails-data/
- **Grails Plugins**: https://grails.apache.org/plugins.html
-- **Groovy 5 Documentation**: https://groovy-lang.org/documentation.html#all-versions (select latest 5.1.x)
+- **Groovy 6 Documentation**: https://groovy-lang.org/documentation.html#all-versions (select latest 6.0.x)
- **Spock Framework**: https://spockframework.org/spock/docs/2.4/all_in_one.html
- **Geb Manual**: https://groovy.apache.org/geb/manual/current/
diff --git a/.agents/skills/groovy-developer/SKILL.md b/.agents/skills/groovy-developer/SKILL.md
index 2b370502be..2af957fa56 100644
--- a/.agents/skills/groovy-developer/SKILL.md
+++ b/.agents/skills/groovy-developer/SKILL.md
@@ -1,6 +1,6 @@
---
name: groovy-developer
-description: Expert guide for Groovy 5 development, covering concise syntax, closures, DSLs, metaprogramming, static compilation, and integration with Java 21 and Grails
+description: Expert guide for Groovy 6 development, covering concise syntax, closures, DSLs, metaprogramming, static compilation, and integration with Java 21 and Grails
license: Apache-2.0
---
<!--
@@ -11,7 +11,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more contributor l
## What I Do
-- Provide deep expertise in Groovy 5.1, including core language features: syntax, closures, traits, GStrings, operators, and Groovy Truth.
+- Provide deep expertise in Groovy 6.0, including core language features: syntax, closures, traits, GStrings, operators, and Groovy Truth.
- Guide metaprogramming techniques: runtime (categories, ExpandoMetaClass, extension modules) and compile-time (AST transformations like @Immutable, @Builder, @Delegate, @Singleton, @Log, @Memoized).
- Assist with DSL creation and usage (MarkupBuilder, JsonBuilder, builders for configuration).
- Support testing with Spock Framework (BDD, data-driven tests, mocking) and built-in power assertions.
@@ -509,7 +509,7 @@ sql.execute("INSERT INTO books (title) VALUES ($title)")
## Resources
-- **Groovy 5 Documentation**: https://groovy-lang.org/documentation.html#all-versions (select latest 5.1.x)
+- **Groovy 6 Documentation**: https://groovy-lang.org/documentation.html#all-versions (select latest 6.0.x)
- **Groovy Style Guide**: https://groovy-lang.org/style-guide.html
- **GORM Documentation**: https://grails.apache.org/docs/latest/grails-data/
- **Spock Framework**: https://spockframework.org/spock/docs/2.4/all_in_one.html
diff --git a/AGENTS.md b/AGENTS.md
index 6c7eaabb4e..12abde794e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -74,7 +74,7 @@ export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G"
| Skill | Path | Use For |
|-------|------|---------|
| **grails-developer** | `.agents/skills/grails-developer/SKILL.md` | Current Grails apps, GORM, controllers, views |
-| **groovy-developer** | `.agents/skills/groovy-developer/SKILL.md` | Groovy 5 syntax, closures, DSLs, Spock |
+| **groovy-developer** | `.agents/skills/groovy-developer/SKILL.md` | Groovy 6 syntax, closures, DSLs, Spock |
| **gradle-developer** | `.agents/skills/gradle-developer/SKILL.md` | Gradle 9 builds, BOM/platforms, convention plugins, wrappers |
| **grails-8-upgrade** | `.agents/skills/grails-8-upgrade/SKILL.md` | Upgrading Grails applications from 7.x to 8 |
| **java-developer** | `.agents/skills/java-developer/SKILL.md` | Java 21 features, Groovy interop |
@@ -88,7 +88,7 @@ export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G"
| Component | Version |
|-----------|---------|
| JDK | 21+ (baseline 21) |
-| Groovy | 5.1.x |
+| Groovy | 6.0.x |
| Spring Boot | 4.1.x |
| Spring Framework | 7.0.x |
| Spock | 2.4-groovy-5.0 |
@@ -309,7 +309,7 @@ and known non-findings — before reporting issues.
## Resources
- **Grails Guide**: https://grails.apache.org/docs/latest/guide/single.html
-- **Groovy 5 Docs**: https://groovy-lang.org/documentation.html#all-versions (select latest 5.1.x)
+- **Groovy 6 Docs**: https://groovy-lang.org/documentation.html#all-versions (select latest 6.0.x)
- **Spock 2.4 Docs**: https://spockframework.org/spock/docs/2.4/all_in_one.html
- **GORM Docs**: https://grails.apache.org/docs/latest/grails-data/
- **Issues**: https://github.com/apache/grails-core/issues
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
index 935185516f..c61250d830 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
@@ -34,6 +34,7 @@ import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.GroovyCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.api.tasks.testing.Test
import org.gradle.external.javadoc.StandardJavadocDocletOptions
import static org.apache.grails.buildsrc.GradleUtils.lookupProperty
@@ -118,11 +119,13 @@ class CompilePlugin implements Plugin<Project> {
if (!it.options.compilerArgs.contains('-parameters')) {
it.options.compilerArgs.add('-parameters')
}
- // Grails 8 keeps invokedynamic off for published artifacts. Groovy 5's
- // compiler default is indy=true, which is a large runtime regression for
- // dynamic Groovy (see #15293). Unpublished build-logic uses Gradle's
- // default. Grails 9 / Groovy 6 can flip this. CI can still opt in with
- // -PgrailsIndy=true (same property as grails-extension-gradle-config.gradle).
+ // Grails 8 keeps invokedynamic off for published artifacts. The Groovy
+ // compiler default is indy=true, which was a large runtime regression for
+ // dynamic Groovy on Groovy 5 (see #15293). Unpublished build-logic uses
+ // Gradle's default. On Groovy 6 classes compiled this way need the
+ // groovy-callsite module at runtime, which grails-common carries. CI can
+ // still opt in with -PgrailsIndy=true (same property as
+ // grails-extension-gradle-config.gradle).
it.groovyOptions.optimizationOptions.put('indy', lookupProperty(project, 'grailsIndy', false))
// encoding needs to be the same since it's different across platforms
it.options.encoding = StandardCharsets.UTF_8.name()
@@ -130,6 +133,13 @@ class CompilePlugin implements Plugin<Project> {
// always set an isolated build to ensure grails.factories aren't accidentally merged since every project
// in this mono repo should be an isolated projected
it.options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx2G', '-Dgrails.isolated.build=true']
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): Spock's global AST transform is on
+ // every compile classpath that carries spock-core and checks the Groovy major
+ // version inside the forked compiler, failing with IncompatibleGroovyVersionException.
+ // Remove when the BOM moves to a Spock build for Groovy 6. App test build: an
+ // application's build gets the same flag from the Grails Gradle plugin.
+ it.groovyOptions.forkOptions.jvmArgs = (it.groovyOptions.forkOptions.jvmArgs ?: []) +
+ ['-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true']
// Publish THIS project's base.dir to the forked Groovy compiler. Gradle reuses a forked
// compiler daemon for a task whose requested fork arguments the daemon already satisfies,
// so a compile that does NOT request base.dir can be handed a daemon started for another
@@ -150,6 +160,13 @@ class CompilePlugin implements Plugin<Project> {
it.groovyOptions.configurationScript =
GradleUtils.findRootGrailsCoreDir(project).file('gradle/groovy-compile-configscript.groovy').asFile
}
+ project.tasks.withType(Test).configureEach {
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): the same check runs when a Spec
+ // starts, and again whenever a test compiles Groovy at runtime with spock-core on
+ // the classpath. A system property survives a later `jvmArgs = [...]` assignment.
+ // Remove with the compiler flag above.
+ it.systemProperty('spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true')
+ }
project.tasks.named('compileGroovy', GroovyCompile).configure { GroovyCompile task ->
// Resource-only changes do not ordinarily invalidate compilation. This file changes
// whether the compiler owns the generated imports resource, so adding or deleting it
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GroovydocRunner.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GroovydocRunner.groovy
index 2f8d9da047..1918b69315 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GroovydocRunner.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GroovydocRunner.groovy
@@ -83,6 +83,10 @@ abstract class GroovydocRunner {
spec.classpath = classpath
spec.mainClass.set('groovy.ui.GroovyMain')
spec.args = [scriptFile.absolutePath, paramsFile.absolutePath]
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): the documentation classpath can carry
+ // spock-core, whose global AST transform refuses to load on Groovy 6 without this.
+ // Remove when the BOM moves to a Spock build for Groovy 6. Framework only.
+ spec.systemProperty('spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true')
spec.maxHeapSize = maxHeapSize
}
}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
index 9bbeca3087..2bb7e9ab98 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy
@@ -113,20 +113,9 @@ class SbomPlugin implements Plugin<Project> {
'pkg:maven/de.bwaldvogel/mongo-java-server@1.47.0?type=jar' : 'BSD-3-Clause',
'pkg:maven/de.bwaldvogel/mongo-java-server-core@1.47.0?type=jar' : 'BSD-3-Clause',
'pkg:maven/de.bwaldvogel/mongo-java-server-memory-backend@1.47.0?type=jar': 'BSD-3-Clause',
- // The whole org.jline group declares "The BSD License", which maps incorrectly because of
+ // The org.jline group is handled by forcedLicenseFor below: every artifact of it declares
+ // "The BSD License", which maps incorrectly because of
// https://github.com/CycloneDX/cyclonedx-core-java/issues/205 - the POMs point at BSD-3-Clause.
- // jline.version tracks the JLine version Groovy ships, so every module resolves to one version.
- 'pkg:maven/org.jline/jansi@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-builtins@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-console@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-native@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-reader@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-style@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-terminal@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-terminal-jansi@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-terminal-jna@3.30.16?type=jar' : 'BSD-3-Clause',
- 'pkg:maven/org.jline/jline-terminal-jni@3.30.16?type=jar' : 'BSD-3-Clause',
'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause', // https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.liquibase.ext/liquibase-hibernate5@4.27.0?type=jar': 'Apache-2.0', // maps incorrectly because of https://github.com/liquibase/liquibase/issues/2445 & the base pom does not define a license
'pkg:maven/org.json/json@20251224?type=jar' : 'Public-Domain', // required due to jedis, https://issues.apache.org/jira/browse/LEGAL-666 approves this usage
@@ -453,6 +442,20 @@ class SbomPlugin implements Plugin<Project> {
}
}
+ private static String forcedLicenseFor(String bomRef) {
+ if (LICENSE_MAPPING.containsKey(bomRef)) {
+ return LICENSE_MAPPING[bomRef]
+ }
+ // Groovy 6 ships on JLine 4 (#16157), and jline.version follows it. JLine's LICENSE.txt is
+ // BSD-3-Clause, but CycloneDX reads "The BSD License" as BSD-4-Clause
+ // (cyclonedx-core-java#205). Limited to the org.jline Maven jar family at 4.x so a bump
+ // inside that line needs no new entry. Framework only.
+ if (bomRef.matches('pkg:maven/org\\.jline/[^/@?]+@4\\.[^?]+\\?type=jar')) {
+ return 'BSD-3-Clause'
+ }
+ null
+ }
+
/**
* Picks the most appropriate license for a dependency from a list of license choices.
* This method is called at execution time and should not access Task.project.
@@ -471,10 +474,10 @@ class SbomPlugin implements Plugin<Project> {
}
logger.info('Picking license for {} from {} choices', bomRef, licenseChoices.size())
- if (LICENSE_MAPPING.containsKey(bomRef)) {
+ def licenseId = forcedLicenseFor(bomRef)
+ if (licenseId) {
// There are several reasons that cyclone will get the license wrong, usually due to upstream not publishing information or publishing it incorrectly
// see the licenseMapping map above for details
- def licenseId = LICENSE_MAPPING[bomRef]
logger.info('Forcing license for {} to {}', bomRef, licenseId)
def licenseBlock = LICENSES[licenseId]
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
index 83721b2219..1238fd1a13 100644
--- a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
@@ -83,6 +83,8 @@ class CompilePluginSpec extends Specification {
println "TEST_INDY=\${testCompileTask.get().groovyOptions.optimizationOptions.indy}"
println "MAIN_JOINT_JAVAC_ARGS=\${compileTask.get().options.compilerArgs}"
println "MAIN_GROOVY_PARAMETERS=\${compileTask.get().groovyOptions.parameters}"
+ println "MAIN_GROOVY_FORK_JVM_ARGS=\${compileTask.get().groovyOptions.forkOptions.jvmArgs}"
+ println "TEST_GROOVY_FORK_JVM_ARGS=\${testCompileTask.get().groovyOptions.forkOptions.jvmArgs}"
}
}
"""
@@ -107,6 +109,15 @@ class CompilePluginSpec extends Specification {
result.output.contains('MAIN_JOINT_JAVAC_ARGS=[-parameters]')
}
+ def "switches off the Spock Groovy version check in the forked Groovy compiler"() {
+ when:
+ def result = runPrintIndy()
+
+ then:
+ result.output.contains('MAIN_GROOVY_FORK_JVM_ARGS=[-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true]')
+ result.output.contains('TEST_GROOVY_FORK_JVM_ARGS=[-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true]')
+ }
+
def "enables invokedynamic when grailsIndy is true"() {
when:
def result = runPrintIndy('-PgrailsIndy=true')
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/SbomPluginSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/SbomPluginSpec.groovy
index 4786a254a9..3d4fe29f9f 100644
--- a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/SbomPluginSpec.groovy
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/SbomPluginSpec.groovy
@@ -81,6 +81,34 @@ class SbomPluginSpec extends Specification {
location.get().asFile.name == 'grails-core-custom-cli-8.0.0-sbom.json'
}
+ private static List bsd4Choice() {
+ [[license: [id: 'BSD-4-Clause']]]
+ }
+
+ void "JLine 4 Maven jars use the BSD-3-Clause correction for any version of that line"() {
+ expect:
+ SbomPlugin.pickLicense(LOGGER, 'grails-console', 'grails-console',
+ 'pkg:maven/org.jline/jansi@4.4.5?type=jar', bsd4Choice()).id == 'BSD-3-Clause'
+ SbomPlugin.pickLicense(LOGGER, 'grails-console', 'grails-console',
+ 'pkg:maven/org.jline/jline-console-ui@4.5.0?type=jar', bsd4Choice()).id == 'BSD-3-Clause'
+ }
+
+ void "the JLine 4 license correction is limited to JLine 4 Maven jars"() {
+ when:
+ SbomPlugin.pickLicense(LOGGER, 'grails-console', 'grails-console', bomRef, bsd4Choice())
+
+ then:
+ GradleException e = thrown(GradleException)
+ e.message.contains('BSD-4-Clause')
+
+ where:
+ bomRef << [
+ 'pkg:maven/com.example/jansi@4.4.5?type=jar',
+ 'pkg:maven/org.jline/jansi@3.30.16?type=jar',
+ 'pkg:maven/org.jline/jansi@4.4.5?type=pom',
+ ]
+ }
+
void "a category-X license is permitted when exempted for the sbom component (cli companion)"() {
expect: "the hibernate 5 LGPL exemption keyed to the dbmigration cli companion is honoured"
SbomPlugin.pickLicense(LOGGER, 'grails-data-hibernate5-dbmigration',
diff --git a/build.gradle b/build.gradle
index 61f25361f5..247df0213d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,6 +31,8 @@ import javax.inject.Inject
import org.apache.tools.ant.taskdefs.condition.Os
+apply from: layout.projectDirectory.file('dependencies.gradle')
+
ext {
isReproducibleBuild = System.getenv("SOURCE_DATE_EPOCH") != null
buildInstant = java.util.Optional.ofNullable(System.getenv("SOURCE_DATE_EPOCH"))
@@ -139,6 +141,21 @@ subprojects {
rootProject.ext['profileProjects'] << name
}
+ // Groovy 6 (#16157, GROOVY-12185): the classic (non-invokedynamic) call-site runtime is the
+ // separate groovy-callsite module, and the compiler refuses to generate that bytecode without it
+ // on the compile classpath ("Classic call-site bytecode generation requires the optional
+ // org.apache.groovy:groovy-callsite module"). Every module compiles with indy off (CompilePlugin),
+ // and the ones below grails-common, which exports the module for everything above it, have no
+ // other way to get it. Permanent while indy stays off. Framework only: applications get it
+ // through grails-common.
+ plugins.withId('groovy') {
+ String groovyCallsite = "org.apache.groovy:groovy-callsite:${bomDependencyVersions['groovy.version']}"
+ sourceSets.configureEach { sourceSet ->
+ dependencies.add(sourceSet.compileOnlyConfigurationName, groovyCallsite)
+ dependencies.add(sourceSet.runtimeOnlyConfigurationName, groovyCallsite)
+ }
+ }
+
configurations.configureEach {
resolutionStrategy {
def cacheHours = isCiBuild || isReproducibleBuild ? 0 : 24
diff --git a/dependencies.gradle b/dependencies.gradle
index 1e8e823280..6ebfeeec15 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -35,7 +35,7 @@ ext {
'javaparser-core.version' : '3.28.2',
// Tracks the JLine version Groovy ships (groovy-groovysh), so the framework and groovysh
// share one terminal stack - keep in step with groovy.version.
- 'jline.version' : '3.30.16',
+ 'jline.version' : '4.4.5',
'jna.version' : '5.19.1',
'jquery.version' : '4.0.0',
'maven-model.version' : '3.9.16',
@@ -85,7 +85,7 @@ ext {
// extended-scalars compatibility. See https://github.com/apache/grails-core/issues/15674
'graphql-java.version' : '25.0',
'graphql-java-extended-scalars.version': '24.0',
- 'groovy.version' : '5.1.3',
+ 'groovy.version' : '6.0.0',
'guava.version' : '33.7.1-jre',
// Groovy YAML 5.1.0 requires a minimum of Jackson 2.22.1, and 2.22.2 also overrides the
// 2.21.5 Spring Boot 4.1 manages: it adds the StreamReadConstraints length limit to
@@ -124,6 +124,11 @@ ext {
'rxjava2.version' : '2.2.21',
'sitemesh.version' : '2.6.0',
'scribejava.version' : '8.3.3',
+ // Groovy 6 (#16157): Spock has no groovy-6.0 build. The groovy-5.0 build runs on Groovy 6
+ // once its Groovy major-version check is switched off with the
+ // spock.iKnowWhatImDoing.disableGroovyVersionCheck system property, which CompilePlugin sets
+ // on every compile and test JVM here and the Grails Gradle plugin sets for applications.
+ // Move to the Groovy 6 build and drop the property when Spock publishes one. App test build.
'spock.version' : '2.4-groovy-5.0',
'starter-sitemesh.version' : '3.3.0-RC1',
'spring-webmvc-sitemesh.version': '3.3.0-RC1',
@@ -160,6 +165,9 @@ ext {
'groovy' : "org.apache.groovy:groovy:${bomDependencyVersions['groovy.version']}",
'groovy-ant' : "org.apache.groovy:groovy-ant:${bomDependencyVersions['groovy.version']}",
'groovy-astbuilder' : "org.apache.groovy:groovy-astbuilder:${bomDependencyVersions['groovy.version']}",
+ // Groovy 6 (#16157, GROOVY-12185): the classic (non-invokedynamic) call-site runtime is a
+ // separate module. Grails compiles with indy off, so grails-common exports it. Permanent.
+ 'groovy-callsite' : "org.apache.groovy:groovy-callsite:${bomDependencyVersions['groovy.version']}",
'groovy-cli-commons' : "org.apache.groovy:groovy-cli-commons:${bomDependencyVersions['groovy.version']}",
'groovy-cli-picocli' : "org.apache.groovy:groovy-cli-picocli:${bomDependencyVersions['groovy.version']}",
'groovy-console' : "org.apache.groovy:groovy-console:${bomDependencyVersions['groovy.version']}",
diff --git a/end-to-end/README.md b/end-to-end/README.md
index 7d2dc6cfb6..35e55797fe 100644
--- a/end-to-end/README.md
+++ b/end-to-end/README.md
@@ -33,13 +33,13 @@ applications at via `GRAILS_REPO_URL`.
| Project | What it is |
|---|---|
| `legacy-g7-command-plugin` | A **standalone build**, not part of this one. Compiles against published Grails 7 / Groovy 4 to produce a genuine precompiled `grails.dev.commands.ApplicationCommand` binary. |
-| `legacy-commands-plugin` | A Grails 8 plugin whose legacy commands are recompiled under Groovy 5. |
+| `legacy-commands-plugin` | A Grails 8 plugin whose legacy commands are recompiled under Groovy 6. |
| `legacy-commands` | A Grails 8 application that consumes both and runs their commands through the registry. |
| `spring-dependency-management` | A Grails 8 application that manages its versions with the legacy `io.spring.dependency-management` plugin instead of the Grails Gradle plugin's native `platform(grails-bom)`, as an upgraded Grails 7 application does. |
| `taglib-index-incremental` | Builds a Grails 8 application **twice, without a clean**, to prove a renamed or deleted tag library cannot survive in the published tag library index. Incremental behaviour is the whole point, so it cannot be expressed by a project the core build builds once for itself. |
`legacy-g7-command-plugin` is deliberately excluded from `settings.gradle`. An included build would
-substitute `org.apache.grails:grails-core` for this repository's Groovy 5 project, which is exactly
+substitute `org.apache.grails:grails-core` for this repository's Groovy 6 project, which is exactly
the substitution the fixture exists to avoid — it must be compiled by a real Grails 7 toolchain for
its trait-woven bytecode to prove anything.
diff --git a/end-to-end/build.gradle b/end-to-end/build.gradle
index 236237fd03..b93c75eb8d 100644
--- a/end-to-end/build.gradle
+++ b/end-to-end/build.gradle
@@ -29,6 +29,17 @@ subprojects {
}
}
+ // Grails 8 compiles against Groovy 6 while Spock ships as 2.4-groovy-5.0 (#16157). The
+ // framework build disables Spock's Groovy version check the same way (see CompilePlugin).
+ // Remove when the BOM moves to a Spock build for Groovy 6.
+ tasks.withType(GroovyCompile).configureEach {
+ groovyOptions.forkOptions.jvmArgs = (groovyOptions.forkOptions.jvmArgs ?: []) +
+ ['-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true']
+ }
+ tasks.withType(Test).configureEach {
+ systemProperty 'spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true'
+ }
+
tasks.withType(AbstractCompile).configureEach {
doFirst {
if (!localMavenRepo.asFile.directory) {
diff --git a/end-to-end/legacy-g7-command-plugin/build.gradle b/end-to-end/legacy-g7-command-plugin/build.gradle
index 43981d6547..4168c282a8 100644
--- a/end-to-end/legacy-g7-command-plugin/build.gradle
+++ b/end-to-end/legacy-g7-command-plugin/build.gradle
@@ -22,7 +22,7 @@
// sources under Grails 8.
//
// This is deliberately not part of the end-to-end build and is not composed with includeBuild:
-// substitution would rewrite org.apache.grails:grails-core to this repo's Groovy 5 project, which
+// substitution would rewrite org.apache.grails:grails-core to this repo's Groovy 6 project, which
// is exactly what the fixture exists to avoid.
//
// It builds under the JDK declared in .sdkmanrc (17, the minimum for a Grails 7 app) rather than a
diff --git a/end-to-end/settings.gradle b/end-to-end/settings.gradle
index 428275ad5b..6880097cf7 100644
--- a/end-to-end/settings.gradle
+++ b/end-to-end/settings.gradle
@@ -121,7 +121,7 @@ include(
// legacy-g7-command-plugin is deliberately NOT included here. It compiles against published
// Grails 7 / Groovy 4 artifacts, and an included build would substitute
-// org.apache.grails:grails-core for this repo's Groovy 5 project - which is exactly the
+// org.apache.grails:grails-core for this repo's Groovy 6 project - which is exactly the
// substitution the fixture exists to avoid. It stays a standalone build, run under its own JDK
// (legacy-g7-command-plugin/.sdkmanrc), and legacy-commands consumes the jar it produces.
diff --git a/gradle/mongodb-forked-test-config.gradle b/gradle/mongodb-forked-test-config.gradle
index b230f36282..2b13265274 100644
--- a/gradle/mongodb-forked-test-config.gradle
+++ b/gradle/mongodb-forked-test-config.gradle
@@ -25,7 +25,9 @@ dependencies {
}
tasks.named('compileTestGroovy', GroovyCompile) {
- groovyOptions.forkOptions.jvmArgs = ['-Xmx768m']
+ // Append rather than assign: CompilePlugin already passes the Spock Groovy version-check
+ // opt-out to this fork (#16157).
+ groovyOptions.forkOptions.jvmArgs = (groovyOptions.forkOptions.jvmArgs ?: []) + ['-Xmx768m']
}
tasks.withType(Test).configureEach {
diff --git a/gradle/mongodb-test-config.gradle b/gradle/mongodb-test-config.gradle
index 8925cf25de..cc3e3cfe5c 100644
--- a/gradle/mongodb-test-config.gradle
+++ b/gradle/mongodb-test-config.gradle
@@ -25,7 +25,9 @@ dependencies {
}
tasks.named('compileTestGroovy', GroovyCompile) {
- groovyOptions.forkOptions.jvmArgs = ['-Xmx768m']
+ // Append rather than assign: CompilePlugin already passes the Spock Groovy version-check
+ // opt-out to this fork (#16157).
+ groovyOptions.forkOptions.jvmArgs = (groovyOptions.forkOptions.jvmArgs ?: []) + ['-Xmx768m']
}
tasks.withType(Test).configureEach {
diff --git a/grails-common/build.gradle b/grails-common/build.gradle
index 2b8ee5f649..0b2b471853 100644
--- a/grails-common/build.gradle
+++ b/grails-common/build.gradle
@@ -42,6 +42,13 @@ dependencies {
api 'org.apache.grails.gradle:grails-gradle-common'
api 'org.apache.groovy:groovy'
+ // Groovy 6 (#16157, GROOVY-12185): the classic (non-invokedynamic) call-site runtime is the
+ // separate groovy-callsite module. Grails compiles with indy off by default; without the module
+ // the compiler refuses to generate such bytecode and the classes fail at runtime with
+ // NoClassDefFoundError for CallSiteArray. Exporting it here puts it on the compile and runtime
+ // classpath of every module and application that reaches grails-common, which is everything
+ // that depends on grails-core. Permanent while indy stays off. App runtime.
+ api 'org.apache.groovy:groovy-callsite'
api 'org.slf4j:jcl-over-slf4j'
api 'org.slf4j:slf4j-api'
api 'org.springframework:spring-context', {
diff --git a/grails-core/src/main/groovy/grails/boot/GrailsBanner.groovy b/grails-core/src/main/groovy/grails/boot/GrailsBanner.groovy
index b6bfbbeaa3..28c14c1a5b 100644
--- a/grails-core/src/main/groovy/grails/boot/GrailsBanner.groovy
+++ b/grails-core/src/main/groovy/grails/boot/GrailsBanner.groovy
@@ -511,7 +511,8 @@ class GrailsBanner implements Banner {
/** What an application wrote under the given property, or nothing where it wrote none. */
private static List<String> readVersionOptions(Environment env, String propertyName) {
- env.getProperty(propertyName, List<String>, [] as List<String>)
+ // Groovy 6.0.0 (GROOVY-12319, #16157): parameterized types are not class literals. Permanent. App source.
+ env.getProperty(propertyName, List, [] as List<String>) as List<String>
}
/**
diff --git a/grails-core/src/main/groovy/grails/config/external/WriterFilteringMap.groovy b/grails-core/src/main/groovy/grails/config/external/WriterFilteringMap.groovy
index 9667f67759..5819ea5318 100644
--- a/grails-core/src/main/groovy/grails/config/external/WriterFilteringMap.groovy
+++ b/grails-core/src/main/groovy/grails/config/external/WriterFilteringMap.groovy
@@ -26,7 +26,10 @@ class WriteFilteringMap implements Map<String, Object> {
String keyPrefix
private Map<String, Object> proxied // source map
- @Delegate
+ // Groovy 6 (#16157): @Delegate now generates the mutators over the tracking overrides below, so
+ // written values were no longer recorded (WriteFilteringMapSpec). Excluded so the overrides stay.
+ // Remove when @Delegate again leaves declared methods alone. App runtime.
+ @Delegate(excludes = ['put', 'putAll', 'remove'])
private Map<String, Object> overlap // written values, flattened -- shared
private Map<String, Object> nestedDestinationMap // written keys at this level
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
index 9ffa2b0bdb..6b2acf0445 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy
@@ -524,7 +524,9 @@ class HibernateGormInstanceApi<D> extends GormInstanceApi<D> {
setObjectToReadOnly target
if (entity) {
for (Association association in entity.associations) {
- if (association instanceof ToOne && !association instanceof Embedded) {
+ // Groovy 6.0.0 (#16157) rejects `!x instanceof T` ("Incompatible instanceof types"),
+ // which parsed as `(!x) instanceof T` and was always false. Permanent. App source.
+ if (association instanceof ToOne && !(association instanceof Embedded)) {
def bean = new BeanWrapperImpl(target)
def propertyValue = bean.getPropertyValue(association.name)
if (propertyValue != null) {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy
index d0fd9b01ed..70573d66c6 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy
@@ -369,6 +369,25 @@ class HibernateGormInstanceApiSpec extends HibernateGormDatastoreSpec {
person.errors.fieldErrors.any { it.field == 'name' }
}
+ @Rollback
+ def "a validation error marks the invalid instance's to-one association read-only"() {
+ given: 'a persisted author and a book that fails validation'
+ def author = new PersonInstanceApi(name: 'Author', age: 40).save(flush: true)
+ def book = new ConstrainedBook(title: '', author: author)
+
+ when:
+ def result = ConstrainedBook.withTransaction {
+ book.save(flush: true)
+ }
+
+ then:
+ result == null
+ book.hasErrors()
+
+ and: 'the author the session still manages cannot be flushed behind the failed save'
+ ConstrainedBook.withSession { it.isReadOnly(author) }
+ }
+
@Rollback
def "save with invalid entity and failOnError:true throws an exception"() {
given:
diff --git a/grails-datamapping-core/src/test/groovy/org/grails/compiler/gorm/GormEntityTransformSpec.groovy b/grails-datamapping-core/src/test/groovy/org/grails/compiler/gorm/GormEntityTransformSpec.groovy
index 3aeac5e308..0f01f808ef 100644
--- a/grails-datamapping-core/src/test/groovy/org/grails/compiler/gorm/GormEntityTransformSpec.groovy
+++ b/grails-datamapping-core/src/test/groovy/org/grails/compiler/gorm/GormEntityTransformSpec.groovy
@@ -229,12 +229,30 @@ class GormEntityTransformSpec extends Specification{
expect: 'all GormEntity methods are marked as Generated on implementation class'
GormEntity.methods.each { Method traitMethod ->
- assert Book.getMethod(traitMethod.name, traitMethod.parameterTypes).isAnnotationPresent(Generated)
+ assert findGeneratedMethod(Book, traitMethod).isAnnotationPresent(Generated)
}
and: 'all GormValidateable methods are marked as Generated on implementation class'
GormValidateable.methods.each { Method traitMethod ->
- assert Book.getMethod(traitMethod.name, traitMethod.parameterTypes).isAnnotationPresent(Generated)
+ assert findGeneratedMethod(Book, traitMethod).isAnnotationPresent(Generated)
+ }
+ }
+
+ // Groovy 6 (#16157) specializes a generic trait method's type-variable parameters to the
+ // implementing class, so Book.merge(Object) from GormEntity<D> is Book.merge(Book). Look the
+ // specialized signature up when the erased one is missing. Remove if Groovy restores the erased
+ // signature. Test only.
+ private static Method findGeneratedMethod(Class targetClass, Method traitMethod) {
+ try {
+ return targetClass.getMethod(traitMethod.name, traitMethod.parameterTypes)
+ } catch (NoSuchMethodException e) {
+ Class[] specializedParameterTypes = traitMethod.genericParameterTypes.withIndex().collect { type, index ->
+ type instanceof java.lang.reflect.TypeVariable ? targetClass : traitMethod.parameterTypes[index]
+ } as Class[]
+ if (specializedParameterTypes.toList() != traitMethod.parameterTypes.toList()) {
+ return targetClass.getMethod(traitMethod.name, specializedParameterTypes)
+ }
+ throw e
}
}
diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingList.groovy b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingList.groovy
index 5ece3e8e33..d3155c7d9c 100644
--- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingList.groovy
+++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingList.groovy
@@ -32,7 +32,10 @@ import groovy.transform.CompileStatic
@CompileStatic
class DirtyCheckingList extends DirtyCheckingCollection implements List {
- @Delegate List target
+ // Groovy 6 (#16157): @Delegate now generates reversed() over the tracking override below, so the
+ // reverse-ordered view came back untracked (DirtyCheckingCollectionSpec). Excluded so the override
+ // stays. Remove when @Delegate again leaves declared methods alone. App runtime.
+ @Delegate(excludes = 'reversed') List target
DirtyCheckingList(List target, DirtyCheckable parent, String property) {
this(target, parent, property, false)
diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingSortedSet.groovy b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingSortedSet.groovy
index 6109b0426c..e6c3a8fc77 100644
--- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingSortedSet.groovy
+++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckingSortedSet.groovy
@@ -30,7 +30,8 @@ import groovy.transform.CompileStatic
@CompileStatic
class DirtyCheckingSortedSet extends DirtyCheckingCollection implements SortedSet {
- @Delegate SortedSet target
+ // See the note on DirtyCheckingList (#16157): reversed() must not be generated over the override.
+ @Delegate(excludes = 'reversed') SortedSet target
DirtyCheckingSortedSet(SortedSet target, DirtyCheckable parent, String property) {
this(target, parent, property, false)
diff --git a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
index 79530c4673..6221f4e81b 100644
--- a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
+++ b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
@@ -1395,7 +1395,8 @@ grails.gorm.default.constraints = {
[[groovy5-behavior-changes]]
==== 28. Apache Groovy 5 Behavior Changes
-Grails 8 upgrades from Apache Groovy 4 to Apache Groovy 5.
+Grails 8 upgrades from Apache Groovy 4 to Apache Groovy 6, so the Groovy 5 changes below apply on the way.
+See <<groovy6,Apache Groovy 6>> for what the move to Groovy 6 adds.
Most applications need no changes, but a few stricter behaviors can affect application code.
===== 28.1 Assigning a static field from a static closure requires the class name
@@ -4093,3 +4094,51 @@ them. Name the connection, as in `@Transactional(connection = 'secondary')`, whe
has to cover its writes.
Applications using a single datasource are unaffected.
+
+[[groovy6]]
+==== 73. Apache Groovy 6
+
+Grails 8 builds on Apache Groovy 6.0. The Groovy 5 changes in <<groovy5-behavior-changes,Apache Groovy 5 Behavior Changes>>
+still apply; https://groovy-lang.org/releasenotes/groovy-6.0.html[the Groovy 6 release notes] list what changed since.
+Groovy 6 needs JDK 17 or later, which Grails 8's JDK 21 baseline already satisfies. Three of its changes touch a
+Grails build:
+
+* **The classic call-site runtime is a separate module.** Groovy 6 moves the non-`invokedynamic` call-site classes out
+ of `groovy` into `org.apache.groovy:groovy-callsite`, and a class compiled with `invokedynamic` off fails with
+ `NoClassDefFoundError: org/codehaus/groovy/runtime/callsite/CallSiteArray` without it, and the Groovy 6 compiler
+ refuses to generate such bytecode unless the module is on the compile classpath. Grails compiles applications that
+ way by default, and `grails-common`, which every application has through `grails-core`, exports the module, so it is
+ on the compile and runtime classpath already. Nothing needs adding, and a plugin or library compiled that way against
+ Groovy 4 or 5 keeps working. Groovy code compiled with `invokedynamic` off outside a Grails module, such as a plain
+ Groovy library in the same build, declares `org.apache.groovy:groovy-callsite` itself.
+* **Spock has no Groovy 6 build yet.** The Grails BOM keeps Spock `2.4-groovy-5.0`, which runs on Groovy 6 once its
+ Groovy major-version check is switched off. The Grails Gradle plugin sets
+ `spock.iKnowWhatImDoing.disableGroovyVersionCheck=true` on every `Test` task it configures; a task that already
+ sets the property keeps its own value. A `Test` task outside a Grails module, for example in a plain Groovy
+ library of a multi-project build that uses the Grails BOM, sets it itself:
+
+[source,groovy]
+----
+tasks.withType(Test).configureEach {
+ systemProperty 'spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true'
+}
+----
+
+* **JLine 4.** Groovy 6 ships `groovysh` on JLine 4.4.x, and the Grails BOM manages `org.jline:jline` at that version
+ for the Grails CLI and the console. An application that uses the JLine 3 API in its own code should check the
+ https://github.com/jline/jline3/releases[JLine 4 release notes] before upgrading.
+
+Four stricter compiler behaviours in Groovy 6.0.0 came up while moving Grails itself; application code with the same
+shape needs the same one-line change:
+
+* A parameterized type is no longer a class literal: write `config.getProperty(name, List, [])` rather than
+ `config.getProperty(name, List<String>, [])`, and `Mock(Foo)` rather than `Mock(Foo<Bar>)`.
+* `!x instanceof T` is rejected. It always parsed as `(!x) instanceof T`, which is never true, so the intended
+ `!(x instanceof T)` may change behaviour where the old form silently did nothing.
+* Under `@CompileStatic` or `@GrailsCompileStatic`, `new Foo(map)` on a class without a map constructor fails with
+ `Target constructor for constructor call expression hasn't been set`. Add `@groovy.transform.MapConstructor` to the class.
+* A Java source that refers to a Groovy class implementing a trait with static methods, such as `Validateable` or
+ a GORM entity, cannot be joint-compiled: the generated stub declares those methods `static abstract` and javac
+ rejects it. Until the Groovy stub generator is fixed, implement the static methods on the Groovy class
+ explicitly (delegating to `Validateable$Trait$Helper`, for instance), or move the Java source out of the joint
+ compilation.
diff --git a/grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy b/grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
index 6e344c0033..6829d83b46 100644
--- a/grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
+++ b/grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
@@ -20,6 +20,7 @@ package grails.plugin.formfields
import groovy.transform.Canonical
import groovy.transform.CompileStatic
+import groovy.transform.MapConstructor
import groovy.transform.Memoized
import groovy.transform.TupleConstructor
@@ -42,6 +43,11 @@ import org.grails.scaffolding.model.property.Constrained
@CompileStatic
@Canonical
@TupleConstructor(includes = ['beanType', 'propertyName', 'propertyType'])
+// Groovy 6.0.0 (#16157): @CompileStatic no longer resolves new BeanPropertyAccessorImpl(map) to the
+// implicit map-style construction ("Target constructor for constructor call expression hasn't been
+// set"). An explicit map constructor keeps that call shape. Keep permanently, or remove when the
+// implicit form compiles again. App source: a statically compiled `new Foo(map)` needs the same.
+@MapConstructor
class BeanPropertyAccessorImpl implements BeanPropertyAccessor {
Object rootBean
diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/BeanPropertyAccessorImplSpec.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/BeanPropertyAccessorImplSpec.groovy
new file mode 100644
index 0000000000..256aa787d4
--- /dev/null
+++ b/grails-fields/src/test/groovy/grails/plugin/formfields/BeanPropertyAccessorImplSpec.groovy
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package grails.plugin.formfields
+
+import grails.core.GrailsApplication
+import spock.lang.Specification
+
+class BeanPropertyAccessorImplSpec extends Specification {
+
+ void "a map of properties constructs the accessor, as the factory does"() {
+ given:
+ def bean = new Object()
+ def grailsApplication = Mock(GrailsApplication)
+ Map<String, Object> params = [
+ rootBean: bean, rootBeanType: Object, pathFromRoot: 'name',
+ beanType: Object, propertyName: 'name', propertyType: String,
+ value: 'value', grailsApplication: grailsApplication,
+ ]
+
+ when:
+ def accessor = new BeanPropertyAccessorImpl(params)
+
+ then:
+ accessor.rootBean.is(bean)
+ accessor.rootBeanType == Object
+ accessor.pathFromRoot == 'name'
+ accessor.beanType == Object
+ accessor.propertyName == 'name'
+ accessor.propertyType == String
+ accessor.value == 'value'
+ accessor.grailsApplication.is(grailsApplication)
+ accessor.domainProperty == null
+ accessor.constraints == null
+ }
+}
diff --git a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
index c4a2a90bed..188ef5c0e2 100644
--- a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
+++ b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy
@@ -99,6 +99,7 @@ import javax.inject.Inject
@CompileStatic
class GrailsGradlePlugin implements Plugin<Project> {
+ protected static final String SPOCK_DISABLE_GROOVY_VERSION_CHECK = 'spock.iKnowWhatImDoing.disableGroovyVersionCheck'
private static final String NATIVE_IMAGE_PLUGIN = 'org.graalvm.buildtools.native'
/** The plugin that compiles the pages, which are half of what a native image has to be told about. */
@@ -279,6 +280,13 @@ class GrailsGradlePlugin implements Plugin<Project> {
project.tasks.withType(GroovyCompile).configureEach { GroovyCompile c ->
c.groovyOptions.optimizationOptions.indy = indyEnabled
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): Spock's global AST transform checks
+ // the Groovy major version inside the forked compiler too. Same property the Test
+ // tasks get, for the same reason; remove with it. App test build.
+ List<String> forkJvmArgs = c.groovyOptions.forkOptions.jvmArgs ?: []
+ if (!forkJvmArgs.any { String arg -> arg.startsWith("-D${SPOCK_DISABLE_GROOVY_VERSION_CHECK}=") }) {
+ c.groovyOptions.forkOptions.jvmArgs = forkJvmArgs + ["-D${SPOCK_DISABLE_GROOVY_VERSION_CHECK}=true".toString()]
+ }
if (preserveParameterNames != null) {
project.logger.info('Grails: Configuring Groovy compilation to preserve parameter names: {}', preserveParameterNames)
@@ -1052,6 +1060,15 @@ ${importStatements}
String grailsEnvSystemProperty = System.getProperty(Environment.KEY)
tasks.withType(Test).configureEach(systemPropertyConfigurer.curry(grailsEnvSystemProperty ?: Environment.TEST.getName()))
+ tasks.withType(Test).configureEach { Test task ->
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): Spock refuses to start on Groovy 6 with
+ // IncompatibleGroovyVersionException unless its major-version check is switched off. An
+ // application that sets the property itself keeps its own value. Remove when the Grails
+ // BOM moves to a Spock build for Groovy 6. App test build.
+ if (!task.systemProperties.containsKey(SPOCK_DISABLE_GROOVY_VERSION_CHECK)) {
+ task.systemProperty(SPOCK_DISABLE_GROOVY_VERSION_CHECK, 'true')
+ }
+ }
tasks.withType(JavaExec).configureEach(systemPropertyConfigurer.curry(grailsEnvSystemProperty ?: Environment.DEVELOPMENT.getName()))
configureToolchainForForkTasks(project)
diff --git a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/AbstractGroovyTemplateCompileTask.groovy b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/AbstractGroovyTemplateCompileTask.groovy
index ff69e8a401..b32707c5aa 100644
--- a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/AbstractGroovyTemplateCompileTask.groovy
+++ b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/AbstractGroovyTemplateCompileTask.groovy
@@ -134,6 +134,17 @@ abstract class AbstractGroovyTemplateCompileTask extends AbstractCompile {
if (jvmArgs) {
javaExecSpec.jvmArgs(jvmArgs)
}
+
+ // Groovy 6 with Spock 2.4-groovy-5.0 (#16157): the view template classpath can
+ // carry Spock's global AST transform, which aborts compilation with
+ // IncompatibleGroovyVersionException when the Groovy major is newer than the
+ // Spock artifact's. This fork gets the same opt-out the Grails Gradle plugin
+ // gives GroovyCompile and Test tasks, unless the build already passes one
+ // through the fork options. Remove when the BOM moves to a Spock build for
+ // Groovy 6. App test build.
+ if (!jvmArgs?.any { String arg -> arg.startsWith('-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=') }) {
+ javaExecSpec.systemProperty('spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'true')
+ }
javaExecSpec.maxHeapSize = compileOptions.forkOptions.memoryMaximumSize
javaExecSpec.minHeapSize = compileOptions.forkOptions.memoryInitialSize
diff --git a/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGradlePluginToolchainSpec.groovy b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGradlePluginToolchainSpec.groovy
index 7f60c02a1f..cb8cf70e63 100644
--- a/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGradlePluginToolchainSpec.groovy
+++ b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/GrailsGradlePluginToolchainSpec.groovy
@@ -120,6 +120,15 @@ class GrailsGradlePluginToolchainSpec extends GradleSpecification {
result.output.contains('HAS_ENV=true')
result.output.contains('MIN_HEAP=768m')
result.output.contains('MAX_HEAP=768m')
+
+ and: 'only Test tasks switch off the Spock Groovy major-version check, and a task that sets it keeps its value'
+ result.output.contains('JAVAEXEC_SPOCK_CHECK=null')
+ result.output.contains('TEST_HAS_ENV=true')
+ result.output.contains('TEST_SPOCK_CHECK=true')
+ result.output.contains('TEST_SPOCK_CHECK_OVERRIDDEN=false')
+
+ and: 'the forked Groovy compiler gets the same override for the Spock AST transform'
+ result.output.contains('COMPILE_FORK_JVM_ARGS=[-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true]')
}
def "BootRun tasks enable Spring Boot console colors"() {
diff --git a/grails-gradle/plugins/src/test/resources/test-projects/fork-settings-defaults/build.gradle b/grails-gradle/plugins/src/test/resources/test-projects/fork-settings-defaults/build.gradle
index 9816aab19d..2852dadfb2 100644
--- a/grails-gradle/plugins/src/test/resources/test-projects/fork-settings-defaults/build.gradle
+++ b/grails-gradle/plugins/src/test/resources/test-projects/fork-settings-defaults/build.gradle
@@ -7,6 +7,17 @@ tasks.register('checkSysProps', JavaExec) {
mainClass = 'does.not.Matter'
}
+tasks.register('checkTestSysProps', Test) {
+ classpath = files()
+ testClassesDirs = files()
+}
+
+tasks.register('checkTestSysPropsOverridden', Test) {
+ classpath = files()
+ testClassesDirs = files()
+ systemProperty 'spock.iKnowWhatImDoing.disableGroovyVersionCheck', 'false'
+}
+
tasks.register('inspectSysProps') {
doLast {
def task = tasks.named('checkSysProps', JavaExec).get()
@@ -14,5 +25,13 @@ tasks.register('inspectSysProps') {
println "HAS_ENV=${sysProps.containsKey('grails.env')}"
println "MIN_HEAP=${task.minHeapSize}"
println "MAX_HEAP=${task.maxHeapSize}"
+ println "JAVAEXEC_SPOCK_CHECK=${sysProps.get('spock.iKnowWhatImDoing.disableGroovyVersionCheck')}"
+ def testTask = tasks.named('checkTestSysProps', Test).get()
+ println "TEST_HAS_ENV=${testTask.systemProperties.containsKey('grails.env')}"
+ println "TEST_SPOCK_CHECK=${testTask.systemProperties.get('spock.iKnowWhatImDoing.disableGroovyVersionCheck')}"
+ def overridden = tasks.named('checkTestSysPropsOverridden', Test).get()
+ println "TEST_SPOCK_CHECK_OVERRIDDEN=${overridden.systemProperties.get('spock.iKnowWhatImDoing.disableGroovyVersionCheck')}"
+ def compile = tasks.named('compileGroovy', GroovyCompile).get()
+ println "COMPILE_FORK_JVM_ARGS=${compile.groovyOptions.forkOptions.jvmArgs}"
}
}
diff --git a/grails-gsp/grails-sitemesh3/src/test/groovy/org/grails/plugins/sitemesh3/Sitemesh3RenderViewMutatorSpec.groovy b/grails-gsp/grails-sitemesh3/src/test/groovy/org/grails/plugins/sitemesh3/Sitemesh3RenderViewMutatorSpec.groovy
index e8751917d5..a6d8ea5591 100644
--- a/grails-gsp/grails-sitemesh3/src/test/groovy/org/grails/plugins/sitemesh3/Sitemesh3RenderViewMutatorSpec.groovy
+++ b/grails-gsp/grails-sitemesh3/src/test/groovy/org/grails/plugins/sitemesh3/Sitemesh3RenderViewMutatorSpec.groovy
@@ -38,7 +38,7 @@ class Sitemesh3RenderViewMutatorSpec extends Specification {
GrailsSiteMeshView siteMeshView() {
new GrailsSiteMeshView(innerView, Mock(ContentProcessor),
- Mock(DecoratorSelector<SiteMeshContext>), Mock(ServletContext), Mock(ViewResolver))
+ Mock(DecoratorSelector) /* Groovy 6.0.0 (GROOVY-12319, #16157): parameterized types are not class literals */, Mock(ServletContext), Mock(ViewResolver))
}
void 'unwraps the SiteMesh view for partial renders without an explicit layout'() {
diff --git a/grails-spring-security/oauth2/plugin/grails-app/services/grails/plugin/springsecurity/oauth2/SpringSecurityOauth2BaseService.groovy b/grails-spring-security/oauth2/plugin/grails-app/services/grails/plugin/springsecurity/oauth2/SpringSecurityOauth2BaseService.groovy
index d72aa857e9..b097464035 100644
--- a/grails-spring-security/oauth2/plugin/grails-app/services/grails/plugin/springsecurity/oauth2/SpringSecurityOauth2BaseService.groovy
+++ b/grails-spring-security/oauth2/plugin/grails-app/services/grails/plugin/springsecurity/oauth2/SpringSecurityOauth2BaseService.groovy
@@ -301,7 +301,8 @@ class SpringSecurityOauth2BaseService {
* @return The role names for a newly registered user
*/
def getRoleNames() {
- def roleNames = grailsApplication.config.getProperty('grails.plugin.springsecurity.oauth2.registration.roleNames', List<String>, ['ROLE_USER'])
+ // Groovy 6.0.0 (GROOVY-12319, #16157): parameterized types are not class literals. Permanent.
+ def roleNames = grailsApplication.config.getProperty('grails.plugin.springsecurity.oauth2.registration.roleNames', List, ['ROLE_USER'])
return roleNames
}
}
diff --git a/grails-testing-support-core/src/main/groovy/org/grails/testing/ParameterizedGrailsUnitTest.groovy b/grails-testing-support-core/src/main/groovy/org/grails/testing/ParameterizedGrailsUnitTest.groovy
index 60e1e7e8ee..291b9a6e45 100755
--- a/grails-testing-support-core/src/main/groovy/org/grails/testing/ParameterizedGrailsUnitTest.groovy
+++ b/grails-testing-support-core/src/main/groovy/org/grails/testing/ParameterizedGrailsUnitTest.groovy
@@ -49,7 +49,8 @@ trait ParameterizedGrailsUnitTest<T> extends GrailsUnitTest {
mockArtefact(cutType)
final String beanName = getBeanName(cutType)
if (beanName != null && applicationContext.containsBean(beanName)) {
- _artefactInstance = applicationContext.getBean(beanName, T)
+ // Groovy 6.0.0 (GROOVY-12319, #16157): a type parameter is not a class literal. Permanent.
+ _artefactInstance = applicationContext.getBean(beanName, cutType)
} else {
_artefactInstance = cutType.newInstance()
applicationContext.autowireCapableBeanFactory.autowireBeanProperties(_artefactInstance, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false)
diff --git a/grails-testing-support-mongodb/src/main/groovy/org/apache/grails/testing/mongo/StartMongoGrailsUnitExtension.groovy b/grails-testing-support-mongodb/src/main/groovy/org/apache/grails/testing/mongo/StartMongoGrailsUnitExtension.groovy
index 0007914d76..46ef63c63e 100644
--- a/grails-testing-support-mongodb/src/main/groovy/org/apache/grails/testing/mongo/StartMongoGrailsUnitExtension.groovy
+++ b/grails-testing-support-mongodb/src/main/groovy/org/apache/grails/testing/mongo/StartMongoGrailsUnitExtension.groovy
@@ -100,7 +100,8 @@ class StartMongoGrailsUnitExtension extends AbstractMongoGrailsExtension impleme
Package[] packagesArray = packages.toArray(new Package[packages.size()])
Map<String, Object> configuration = ['grails.mongodb.url': createConnectionString(container.getHost(), container.getMappedPort(DEFAULT_MONGO_PORT))]
- def datastore = mongoDatastoreClass.getDeclaredConstructor(Map<String, Object>, Package[]).newInstance(configuration, packagesArray)
+ // Groovy 6.0.0 (GROOVY-12319, #16157): parameterized types are not class literals. Permanent.
+ def datastore = mongoDatastoreClass.getDeclaredConstructor(Map, Package[]).newInstance(configuration, packagesArray)
mongoDatastoreField.writeValue(invocation.sharedInstance, datastore)
}
}
diff --git a/grails-validation/src/test/groovy/grails/validation/ValidateableTraitSpec.groovy b/grails-validation/src/test/groovy/grails/validation/ValidateableTraitSpec.groovy
index 06b1cb9aa1..76cb3f26b4 100644
--- a/grails-validation/src/test/groovy/grails/validation/ValidateableTraitSpec.groovy
+++ b/grails-validation/src/test/groovy/grails/validation/ValidateableTraitSpec.groovy
@@ -347,6 +347,19 @@ class MyValidateable implements Validateable {
String town
private String _someProperty = 'default value'
+ // Groovy 6.0.0 joint compilation (#16157): SomeJavaClass.java references this class, so the
+ // Groovy compiler writes a Java stub for it, and its stub generator emits a precompiled trait's
+ // static methods as `static abstract`, which javac rejects. Implementing them here keeps the
+ // stub legal. Remove when the stub generator handles static trait methods. App source: a Java
+ // class referencing a Groovy class that implements Validateable hits the same failure.
+ static Map<String, Constrained> getConstraintsMap() {
+ Validateable$Trait$Helper.getConstraintsMap(MyValidateable)
+ }
+
+ static void clearConstraintsMapCache() {
+ Validateable$Trait$Helper.clearConstraintsMapCache(MyValidateable)
+ }
+
void setSomeOtherProperty(String s) {}
void setSomeProperty(String s) {
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/GrailsJsonViewHelper.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/GrailsJsonViewHelper.groovy
index 114317700e..9d3a47619a 100644
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/GrailsJsonViewHelper.groovy
+++ b/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/GrailsJsonViewHelper.groovy
@@ -45,7 +45,15 @@ interface GrailsJsonViewHelper extends GrailsViewHelper {
* @param arguments The named arguments: 'template', 'collection', 'model', 'var' and 'bean'
* @return The unescaped JSON
*/
- JsonOutput.JsonWritable render(Map arguments)
+ // Groovy 6.0.0 (#16157): the concrete render(...) overrides in DefaultGrailsJsonViewHelper get a
+ // different return-type descriptor from these methods when JsonOutput.JsonWritable, a nested
+ // class of the joint-compiled JsonOutput.java, is resolved, so the abstract-method check
+ // reports them unimplemented ("Can't have an abstract method in a non-abstract class").
+ // Declared default so the check has nothing to flag; every implementor overrides them. Remove
+ // when the compiler resolves both sides alike. Framework only.
+ default JsonOutput.JsonWritable render(Map arguments) {
+ throw new UnsupportedOperationException()
+ }
/**
* Renders the given object to JSON, typically a domain class, ignoring lazy and internal properties
@@ -55,7 +63,9 @@ interface GrailsJsonViewHelper extends GrailsViewHelper {
* @param customizer Used to customize the contents
* @return The unescaped JSON
*/
- JsonOutput.JsonWritable render(Object object, Map arguments, @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer)
+ default JsonOutput.JsonWritable render(Object object, Map arguments, @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer) {
+ throw new UnsupportedOperationException()
+ }
/**
* Renders the given object to JSON, typically a domain class, ignoring lazy and internal properties
@@ -64,7 +74,9 @@ interface GrailsJsonViewHelper extends GrailsViewHelper {
* @param arguments The supported named arguments: 'includes' or 'excludes' list
* @return The unescaped JSON
*/
- JsonOutput.JsonWritable render(Object object, Map arguments)
+ default JsonOutput.JsonWritable render(Object object, Map arguments) {
+ throw new UnsupportedOperationException()
+ }
/**
* Renders the given object to JSON, typically a domain class, ignoring lazy and internal properties
@@ -72,7 +84,9 @@ interface GrailsJsonViewHelper extends GrailsViewHelper {
* @param object The object to render
* @return The unescaped JSON
*/
- JsonOutput.JsonWritable render(Object object)
+ default JsonOutput.JsonWritable render(Object object) {
+ throw new UnsupportedOperationException()
+ }
/**
* Renders the given object to JSON, typically a domain class, ignoring lazy and internal properties
@@ -81,7 +95,9 @@ interface GrailsJsonViewHelper extends GrailsViewHelper {
* @param customizer the customizer
* @return The unescaped JSON
*/
- JsonOutput.JsonWritable render(Object object, @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer)
+ default JsonOutput.JsonWritable render(Object object, @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer) {
+ throw new UnsupportedOperationException()
+ }
/**
* Renders the given object inline within the current JSON object instead of creating a new JSON object
diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/internal/TemplateRenderer.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/internal/TemplateRenderer.groovy
index 905a7ad385..3073ce9cda 100644
--- a/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/internal/TemplateRenderer.groovy
+++ b/grails-views-gson/src/main/groovy/grails/plugin/json/view/api/internal/TemplateRenderer.groovy
@@ -19,8 +19,10 @@
package grails.plugin.json.view.api.internal
+import groovy.json.StreamingJsonBuilder
import groovy.transform.CompileStatic
+import grails.plugin.json.builder.JsonOutput
import grails.plugin.json.view.api.GrailsJsonViewHelper
import grails.util.GrailsNameUtils
@@ -39,6 +41,39 @@ class TemplateRenderer {
this.jsonViewHelper = jsonViewHelper
}
+ // Groovy 6.0.0 (#16157): @Delegate no longer satisfies the abstract-method check for the five
+ // render(...) overloads, whose return type is a nested class of the joint-compiled
+ // JsonOutput.java ("Can't have an abstract method in a non-abstract class"). The void
+ // inline(...) overloads are unaffected, so @Delegate still handles them. Same behaviour as the
+ // forwarders @Delegate generated on Groovy 5. Remove when @Delegate handles the return type.
+ // Framework only.
+
+ @Override
+ JsonOutput.JsonWritable render(Map arguments) {
+ jsonViewHelper.render(arguments)
+ }
+
+ @Override
+ JsonOutput.JsonWritable render(Object object, Map arguments,
+ @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer) {
+ jsonViewHelper.render(object, arguments, customizer)
+ }
+
+ @Override
+ JsonOutput.JsonWritable render(Object object, Map arguments) {
+ jsonViewHelper.render(object, arguments)
+ }
+
+ @Override
+ JsonOutput.JsonWritable render(Object object) {
+ jsonViewHelper.render(object)
+ }
+
+ @Override
+ JsonOutput.JsonWritable render(Object object, @DelegatesTo(StreamingJsonBuilder.StreamingJsonDelegate) Closure customizer) {
+ jsonViewHelper.render(object, customizer)
+ }
+
@Override
Object invokeMethod(String name, Object args) {
Object[] argArray = (Object[]) args
diff --git a/grails-views-gson/src/test/groovy/grails/plugin/json/view/TemplateRendererSpec.groovy b/grails-views-gson/src/test/groovy/grails/plugin/json/view/TemplateRendererSpec.groovy
index ddcffecfaf..1cb608d05f 100644
--- a/grails-views-gson/src/test/groovy/grails/plugin/json/view/TemplateRendererSpec.groovy
+++ b/grails-views-gson/src/test/groovy/grails/plugin/json/view/TemplateRendererSpec.groovy
@@ -27,6 +27,29 @@ import spock.lang.Specification
*/
class TemplateRendererSpec extends Specification {
+ void "the render overloads of the view helper are available on the template renderer and forward to it"() {
+ given: "a template renderer"
+ def mockViewHelper = Mock(GrailsJsonViewHelper)
+ def tmpl = new TemplateRenderer(mockViewHelper)
+ def o = new Object()
+ def customizer = { -> }
+
+ when:
+ tmpl.render(template: 'foo')
+ tmpl.render(o)
+ tmpl.render(o, [includes: ['name']])
+ tmpl.render(o, customizer)
+ tmpl.render(o, [excludes: ['name']], customizer)
+
+ then:
+ 1 * mockViewHelper.render([template: 'foo'])
+ 1 * mockViewHelper.render(o)
+ 1 * mockViewHelper.render(o, [includes: ['name']])
+ 1 * mockViewHelper.render(o, customizer)
+ 1 * mockViewHelper.render(o, [excludes: ['name']], customizer)
+ 0 * _
+ }
+
void "Test template renderer calls the correct render method"() {
given:"A template renderer"