Grails 9 on Groovy 6.0.0-beta-2 - #15558
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Per @jdaugherty review on #15558 (comment): > This defeats the entire purpose of this plugin. We should not wholesale > map these. every version has to be checked because at any time a license > can change. We need to review these individually > > FYI: if these are really wrong, we should be pushing upstream on cyclone > or the jline project itself to fix their licensing. Both points are correct. The SBOM plugin's value is exactly that each artifact-version is auditable, and a wholesale group rule erases that guarantee the moment a transitive bumps onto a new major. Drop the LICENSE_GROUP_MAPPING map and the matching group-fallback branch in pickLicense, and go back to per-version entries with explicit provenance. Per-version replacements added (each carries the upstream-versioned LICENSE.txt URL inline so future maintainers can re-verify on the next SNAPSHOT bump): pkg:maven/org.jline/jansi@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline@3.30.6 BSD-3-Clause (direct) pkg:maven/org.jline/jline-builtins@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-console@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-console-ui@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-native@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-reader@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-shell@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-style@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-terminal@4.0.12 BSD-3-Clause pkg:maven/org.jline/jline-terminal-jni@4.0.12 BSD-3-Clause Each was verified against https://github.com/jline/jline3/blob/jline-parent-<version>/LICENSE.txt which carries the BSD-3-Clause text. The cyclonedx-core-java#205 misclassification (BSD-4-Clause) is the same root issue we have for the 2.14.6 / antlr4 entries. The 3.30.9 and 4.0.7 entries from the merge with grails8-groovy5-sb4 are dropped because Groovy 6.0.0-SNAPSHOT now resolves the entire org.jline:* group to 4.0.12 transitively via groovy-groovysh; verified with `:grails-shell-cli:dependencies --configuration runtimeClasspath` plus the `Forcing license for ...` log lines on cyclonedxBom. If a future SNAPSHOT bumps onto a new major (5.x), we add fresh per-version entries with re-verified provenance, exactly as the SBOM plugin intends. Verified locally: ./gradlew :grails-shell-cli:cyclonedxBom :grails-console:cyclonedxBom \ :grails-dependencies-starter-web:cyclonedxBom \ -PskipCodeStyle --rerun-tasks -> BUILD SUCCESSFUL in 1m 56s Assisted-by: claude-code:claude-opus-4-7
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Final wrap-up: Groovy 5 / Groovy 6 workaround audit completeThis is the final state of the workaround audit after end-to-end integration testing against Apache Groovy 6.0.0-SNAPSHOT master HEAD (build 508-516; verified that builds 509-516 add only Javadoc + 1 build-infra refactor on top of build 508 - so no functional delta vs. master HEAD). Bottom line5 Groovy 5 workarounds removed in this audit. 7 confirmed required. 2 will be removable when 2 OPEN upstream PRs merge. 1 is Spring 7 specific. 3 untestable locally (Forge canary red for unrelated reasons). Methodology recap
Verified upstream PRs that unblock our remaining workarounds (NOT yet merged to master)
Verified upstream JIRAs already in build 508+ master HEAD
Issues that NEED to be filed upstream (no matching JIRA / PR found)
Forge integration tests (3 workarounds remaining there)
Net delta
Assisted-by: claude-code:claude-opus-4-7 |
Final wrap-up: workaround burndown audit completeAfter end-to-end testing against Burndown count
Verified upstream PRs unblock our remaining workarounds
Verified upstream JIRAs already in master and removed our workaround
Issues to file upstream (no matching JIRA / PR found in
|
aadc3ff to
878adaf
Compare
2bd7667 to
3f485f3
Compare
Audit pass against Groovy 6.0.0-SNAPSHOT build #518 (2026-04-27)Pulled the latest snapshot from Apache snapshots (build #518, timestamp 2026-04-27 14:33:02 UTC; tracks apache/groovy master HEAD at 2026-04-27 15:50 UTC modulo CI lag) and re-verified every Groovy 6 workaround on this branch. Inherited the Groovy 5 audit results from #15557 via merge. Workarounds with confirmed upstream fix in flight
All three are OPEN as of build #518; bug confirmed still present. When each merges + a fresh snapshot publishes, the corresponding workaround can be reverted. Standalone reproducers published for the four real Groovy 6 regressions still needing upstream filing
Each repo has a self-contained build, README pinned to Java 21 + Gradle 9.4.1, and toggles for Groovy 4/5/6 + indy=true/false. Reverting any of the corresponding Grails workarounds and re-running the related test on this branch reproduces the cited failure. Inherited-from-#15557 workarounds re-verified on Groovy 6
Removed since Groovy 5 (Groovy 6 fixed them)
Net effectWorkaround surface area on this canary is now:
cc @paulk-asert - the four "no upstream PR yet" reproducers ( The PR description has the full per-site inventory. |
Re-audit 2026-05-02 against Groovy 6.0.0-SNAPSHOT build #546Pulled latest Headline findingThe three upstream PRs that the previous audit flagged as OPEN all merged on 2026-05-01, after the previous canary baseline (build #518, 2026-04-27) was cut. Build #546 contains all three fixes:
Workarounds removed in this push (5 sites, 1 commit:
|
CI fix - 2026-05-02 follow-upThe SymptomRoot causeGroovy 6's Java stub generator now unconditionally emits I confirmed by trying Fix in
|
2026-05-04 audit pass against Groovy 6.0.0-SNAPSHOT build #571Pulled New Groovy 6 fixes since the previous audit (build #546 → #571)
Local compilation against build #571 (Java 21): Both BUILD SUCCESSFUL. Runtime validation deferred to CIThe GROOVY-11982 fix is at the bytecode The GROOVY-11980 fix is a Java stub generator change. Compilation succeeds locally, so the stub for Remaining real Groovy 6 regressions (no upstream PR yet)Re-verified failing against build #571 by reverting locally:
Each has a deterministic standalone reproducer on Groovy 6.0.0-SNAPSHOT build #571. None has been filed upstream yet - will get tickets opened against Pre-existing canary issue (independent)
Net effectFive workarounds dropped against this round of upstream fixes: 2 directly on this canary (GROOVY-11980, GROOVY-11982), 2 inherited from #15557 via merge (GROOVY-11983 × 2), and the cc @jdaugherty for visibility on the canary progress; @paulk-asert if any of the remaining no-upstream-PR-yet items would benefit from a JIRA ticket sooner rather than later. |
Burn-down pass: 2026-05-08, against Groovy 6.0.0-SNAPSHOT build #609Diff vs the previous push (canary
Local verification (JDK 21, against the cached 6.0.0-SNAPSHOT publication Full integration validation (Hibernate5, Functional, Mongodb under both Standing position after this push: zero Groovy-6-only workarounds remain on this canary. The five remaining workarounds in the description are all inherited from #15557 and reproduce identically on Groovy 5.0.6-SNAPSHOT and on Groovy 6.0.0-SNAPSHOT. GROOVY-11985 (Validateable trait-static dispatch) is the only one with an open upstream ticket; the other four ("VariableScopeVisitor NPE in canonicalisation", "indy=false controller-parameter scope loss", "ConfigurationBuilder + AbstractConstraint static-init", "GROOVY-6362/GROOVY-11817 cc @paulk-asert - thanks for GROOVY-11986; verified clean on build #609. |
2026-05-13 audit pass against Groovy 6.0.0-SNAPSHOT build #645Pulled Snapshot audit window (build #609 -> build #645)Diff'd
None overlap with the remaining workaround inventory. Brought forward from
|
| Build | Test 1 (direct call) | Test 2 (this.defaultNullable() from trait body) |
Test 3 (reflection workaround) |
|---|---|---|---|
| Apache snapshot 6.0.0-20260513.133635-645 (master HEAD, NO PR #2529) | PASS | FAIL - override hijacked back to trait helper default | PASS |
Local build of apache/groovy#2529 HEAD 9115a3de rebased on master |
PASS | PASS - override seen by trait body | PASS |
$ ./gradlew run -PgroovyVersion=6.0.0-20260513.133635-645
Test 2: trait body sees `this.defaultNullable()` from inside the trait
result: false (expected: true if override is honoured, false if hijacked by trait helper)
FAIL - override hijacked back to trait helper default
$ ./gradlew run -PgroovyVersion=6.0.0-SNAPSHOT # mavenLocal, post-PR-2529 build
Test 2: trait body sees `this.defaultNullable()` from inside the trait
result: true (expected: true if override is honoured, false if hijacked by trait helper)
PASS - override seen by trait body
Layer 2: real Grails Validateable workaround removed, validation tests run
Removed the resolveDefaultNullable(Class<?>) reflection shim from grails-validation/.../Validateable.groovy and reverted both call sites to direct unqualified defaultNullable() calls (the natural shape the trait was originally written in):
- boolean isDefaultNullable = resolveDefaultNullable(this)
+ boolean isDefaultNullable = defaultNullable()
...
- boolean isDefaultNullable = resolveDefaultNullable(this.class)
+ boolean isDefaultNullable = defaultNullable()
...
- private static boolean resolveDefaultNullable(Class<?> clazz) {
- ... // 18-line reflective dispatch helper removed
- }Then republished the local Groovy build as the unique version 6.0.0-PR2529-SNAPSHOT (so Gradle resolution couldn't fall back to the published Apache snapshot of plain 6.0.0-SNAPSHOT) and pointed dependencies.gradle at that version. Ran the full ValidateableTraitSpec suite:
./gradlew :grails-validation:compileGroovy :grails-validation:test \
--tests grails.validation.ValidateableTraitSpec --rerun-tasks --no-daemon
Confirmed via build log that the test was executing against 6.0.0-PR2529-SNAPSHOT:
Executing Spock 2.4.0-groovy-5.0 with NOT compatible Groovy version 6.0.0-PR2529-SNAPSHOT
Result: 14 of 14 ValidateableTraitSpec tests PASS (1m 41s, BUILD SUCCESSFUL), including the two that fail without Paul's fix when the workaround is removed:
| Test | Without workaround, against build #645 (no fix) | Without workaround, against PR #2529 |
|---|---|---|
Test that constraints are nullable by default if overridden and ensure nullable:true constraint is not applied when no other constraints were defined by user |
FAIL - constraints.size() == 3 got 4, stray name:nullable:true from override-not-seen |
PASS |
Test that properties defined in a class with overridden defaultNullable which are not explicitly constrained are not accessed during validation |
FAIL - UnsupportedOperationException: getName() should not have been called during validation, override-not-seen forced unconstrained-property access |
PASS |
| Other 12 ValidateableTraitSpec cases | PASS | PASS |
Conclusion
apache/groovy#2529 fully resolves the GROOVY-11985 workaround on this canary. Once it merges to apache/groovy master and a snapshot publishes containing the fix, the Validateable.resolveDefaultNullable(Class<?>) reflection shim can be removed and both call sites can revert to plain defaultNullable(). The local validation edit, the unique-version Groovy republish, and the dependencies.gradle pin were all reverted before this comment; the canary tree is back at d3384e9395.
Thanks @paulk-asert - this clears the only one of the five remaining inherited workarounds that had an open upstream ticket. The other four (VariableScopeVisitor canonicalisation NPE, indy=false controller-parameter scope loss, ConfigurationBuilder + AbstractConstraint static-init, GROOVY-6362 / GROOVY-11817 g taglib regression) still need standalone reproducers filed against apache/groovy.
2026-05-20 burn-down audit against Groovy 6.0.0-SNAPSHOT build #692Per-cycle audit on the upgraded snapshot baseline. Snapshot signals
Workaround burn-down attempts on #692 Each workaround was removed locally (reverting to the pre-Groovy-5 shape) and the targeted test was run.
Net result: 0 of 5 workarounds removable on Groovy 6.0.0-SNAPSHOT #692. All five fire identically to the prior audit baseline. Branch hygiene The base branch (
Merge commit: 0ea4f26. CI is the authoritative next signal. |
Groovy 6 Verifier regression on
|
| # | Attempt | Result |
|---|---|---|
| 1 | Add 5 explicit @Override render(...) forwarders for the default-arg form |
Same 5 errors |
| 2 | Fully qualify return type to grails.plugin.json.builder.JsonOutput.JsonWritable at every site |
Same 5 errors |
| 3 | Rename the inner class JsonOutput.JsonWritable -> JsonOutput.GrailsJsonWritable (eliminates the name shadowing with groovy.json.JsonOutput.JsonWritable) |
Same 5 errors with the renamed type |
| 4 | Remove @CompileStatic from DefaultGrailsJsonViewHelper |
Same 5 errors. Confirms the bug is at the Verifier layer, not the STC. |
| 5 | Remove @CompileStatic from GrailsJsonViewHelper interface |
Same 5 errors |
| 6 | Replace @InheritConstructors with an explicit DefaultGrailsJsonViewHelper(GrailsView) constructor |
Same 5 errors when other annotations are also present |
| 7 | Mark DefaultGrailsJsonViewHelper abstract + create concrete subclass ConcreteGrailsJsonViewHelper extends DefaultGrailsJsonViewHelper |
Suppresses bug on the abstract parent but fires identically on the concrete subclass - the Verifier check is per-class |
| 8 | Mark the concrete subclass @CompileDynamic |
Same 5 errors. Confirms the bug ignores @CompileDynamic. |
| 9 | Remove extends GrailsViewHelper from GrailsJsonViewHelper (breaks the diamond inheritance of GrailsViewHelper between the parent class chain and the interface chain) |
Only superficially suppresses the bug. The build then halts on 2 STC errors in DefaultHalViewHelper (viewHelper.link(Map) is no longer reachable through the interface). Once those are fixed (e.g. via ((GrailsViewHelper) viewHelper).link(...) casts), the abstract-method bug re-fires on DefaultGrailsJsonViewHelper. The diamond-removal does not actually fix anything - it just delays the bug until the build progresses past the link calls. |
| 10 | All of #9 PLUS remove the covariant getG() override from the JsonView trait (so it inherits the parent trait's GrailsViewHelper getG()) |
Same outcome as #9 - bug re-fires once the build progresses |
Annotation-isolation matrix
With every other change reverted to baseline and one annotation at a time on the class header:
| Class annotations | Result |
|---|---|
@CompileStatic @InheritConstructors @Slf4j (original) |
5 errors |
@InheritConstructors @Slf4j (no @CompileStatic) |
5 errors |
@CompileStatic @Slf4j + explicit constructor |
5 errors |
@CompileStatic alone + explicit constructor |
2 STC errors (unrelated log undeclared - confirms abstract-method bug is gone with @Slf4j removed) |
@Slf4j alone + explicit constructor |
5 errors |
@InheritConstructors alone |
5 errors |
| No annotations + explicit constructor | 5 errors |
| No annotations, no explicit constructor | 1 error (missing constructor - compile aborts before the abstract-method check fires) |
The matrix shows the abstract-method check fires on every class configuration that can compile far enough to reach the check. The bug is not gated by any annotation or transform - it is a fundamental Verifier defect for this inheritance shape on Groovy 6.
Minimal-reproducer status
Saved at groovy6-inner-abstract-class-stc-bug/ (will push to a public repo for upstream filing). The reproducer mirrors the structural pattern:
- Java outer class with inner abstract class shadowing the parent's inner class (
reproducer.JsonOutput.JsonWritableshadowinggroovy.json.JsonOutput.JsonWritable) @CompileStaticinterface chainMyInterface extends ParentInterface extends LinkGeneratorwith 5render(...)overloads returning the inner abstract class- Concrete class
@CompileStatic @InheritConstructors MyImpl extends MyIntermediateBase extends MyBase implements MyInterface, ParentInterface(diamond)
The reproducer compiles cleanly on Groovy 6.0.0-SNAPSHOT build #700. Some additional element of the real grails-views-gson codebase is required to trigger the Verifier path; I have not isolated it yet. The grails-views-gson source itself is the working reproducer for now (open source, fully self-contained module).
Recommendation
Three paths forward, none ideal:
- File the upstream ticket with grails-views-gson itself as the reproducer; track as workaround Patch for GRAILS-6695 #6 (blocked, upstream-only) until the Groovy 6 release picks up the fix. CI stays red on this matrix entry.
- Pin the canary to an older Groovy 6 snapshot if there is a build prior to whichever one introduced the regression; needs a git-bisect across
apache/groovyto find the offending commit. - Disable
:grails-views-gson:compileGroovyon the canary as a known-failing-quarantine entry. Loses regression coverage.
Happy to take direction on any of these. The four CI fixes in 7156ed8e3a (jansi@4.1.0 license, asm 9.10 BOM overrides, TemplateRenderer 5 forwarders, BeanPropertyAccessorImpl @MapConstructor) are mechanical and stand on their own; they remove 3 of the 4 distinct CI failure categories the canary was hitting before the merge.
Burn-down audit 2026-05-29 against Groovy 6.0.0-SNAPSHOT build #716Pulled Removed this cycle
Already cleared via the base merge
Re-audited and KEPT (no upstream fix; fire identically on 5.0.7-SNAPSHOT and 6.0.0-SNAPSHOT #716)
Groovy-6-only blocker - still red
NetThe canary now carries one fewer workaround than the Groovy 5 base - the GROOVY-12040 The CI matrix on this push is the authoritative gate; the Assisted-by: claude-code:claude-4.8-opus |
Blocker #6 resolved - Groovy 6 Verifier abstract-method regression worked around (
|
| Task | Result |
|---|---|
:grails-views-gson:compileGroovy |
green |
:grails-views-gson:test |
all pass (render / HAL / JSON-API / template-inheritance incl. g.render(..)); 1 pre-existing @IgnoreIf skip |
:grails-views-gson:codeStyle |
green |
The render-path tests exercise the real implementations, confirming the default bodies are never hit at runtime.
Standalone reproducer status
I built a faithful structural mirror (repro6b): a Java outer class extending groovy.json.JsonOutput with a shadowing inner JsonWritable, the @CompileStatic interface chain JsonViewHelper extends ViewHelper extends LinkGen, the diamond (DefaultJsonViewHelperBase extends DefaultViewHelper implements ViewHelper; Impl extends ... implements JsonViewHelper), default-argument render, mixed void inline(...), joint Java+Groovy compilation, precompiled-jar split, and anonymous JsonWritable subclass instances. It compiles cleanly on build #716 - i.e. none of those ingredients in isolation trigger the defect (matching the earlier finding). The real grails-views-gson module remains the working in-tree reproducer; isolating the last differentiating element into a dependency-free case is still open and will accompany the upstream Apache Groovy ticket.
Net
The canary now builds, tests, and style-checks end-to-end on build #716; the only remaining non-production crutch is the Spock disableGroovyVersionCheck bridge (still DRAFT/DO-NOT-MERGE until a Spock *-groovy-6.0 artifact ships). Inherited workarounds are unchanged (#1 Validateable / GROOVY-11985, #2 VariableScopeVisitor canonicalization, #3 boot4-disabled-integration-test-config, #4 AbstractConstraint static-init).
Assisted-by: claude-code:claude-4.8-opus
Canary update: downstream fixes after the Verifier workaround - CI now fully greenFollowing the blocker-#6 comment (which covered Fixes (in order)
Spock: is a Groovy-6 build needed right now? No.Investigated thoroughly. No Groovy-6-compatible Spock artifact exists anywhere (verified 2026-05-29): Maven Central tops out at The key realization: the "weird runtime errors" the CIFull matrix green on The PR description has been trimmed to track only the remaining workarounds. Assisted-by: claude-code:claude-4.8-opus |
Verified against a real Groovy-6 Spock (spockframework/spock#2363)Confirmed locally that this canary builds and its tests pass against a genuine Groovy-6 Spock build - i.e. the eventual exit path for the Setup
Results - both ran on
One caveat worth recording: the Takeaway: once a Assisted-by: claude-code:claude-4.8-opus |
Bumps groovy.version to 6.0.0-SNAPSHOT (from 5.0.3) to see what breaks. Snapshot resolves from https://repository.apache.org/content/groups/snapshots which was already configured in build-logic/GrailsRepoSettingsPlugin.groovy for the org.apache.groovy.* group. Changes needed on top of the Groovy 5.0.3 canary: - gradle/test-config.gradle: apply '-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true' to every GroovyCompile task, not just compileGroovy/compileTestGroovy. Spock 2.4-groovy-5.0 is the latest available and refuses to run against Groovy 6 without this flag; since SpockTransform is registered via META-INF/services, the Groovy compiler loads it for every source set (including main) and main compiles fail without the flag being set globally. - DefaultHalViewHelper.groovy: reorder the (association instanceof ToMany && !(association instanceof Basic)) / else if (association instanceof ToOne) cascade to check ToOne first. Groovy 6's flow typing narrows 'association' in the else branch in a way that conflicts with the later 'instanceof ToOne' check (Incompatible instanceof types: Basic and ToOne). The reordered form is equivalent because ToOne and ToMany are sibling Association subtypes. - AbstractHibernateGormInstanceApi.groovy: fix a pre-existing operator-precedence bug caught by Groovy 6's stricter instanceof type checking. before: if (association instanceof ToOne && !association instanceof Embedded) { after: if (association instanceof ToOne && !(association instanceof Embedded)) { Without the parentheses '!association' is evaluated first (to a boolean) and then 'instanceof Embedded' is checked against a boolean, which is always false - the whole left side of the && had been dead code. Groovy 6 now reports this as 'Incompatible instanceof types: boolean and Embedded'. Known still-failing: grails-geb:compileTestFixturesGroovy still triggers the ASM Frame.putAbstractType bug that was the reason we pinned to Groovy 5.0.3. Same bytecode-generation issue carries forward to 6.0.0-SNAPSHOT.
Groovy 6.0.0-SNAPSHOT generates invalid bytecode for constructors that use a default-valued List parameter inside @CompileStatic classes. Decompiled stack frames show Object where ArrayList is expected: Type 'java/lang/Object' (current frame, stack[4]) is not assignable to 'java/util/ArrayList' at DefaultConstraintFactory.<init>(Class, MessageSource):V This breaks every validateable. At runtime VerifyError is raised the first time the default-parameter overload is constructed, which cascades into Validateable.validate(), grails-datastore-core bean wiring, and any test that exercises constraints. Workaround: replace the default-parameter signature with two explicit constructors (the 2-arg one delegates to the 3-arg one with [Object.class] as List<Class>). This is compilation-compatible - users were already allowed to construct with or without the targetTypes arg.
Add spock.iKnowWhatImDoing.disableGroovyVersionCheck to all shared test configs (hibernate5, mongodb, mongodb-forked, functional) via tasks.withType(GroovyCompile).configureEach. The flag was only in test-config.gradle, so modules using other configs failed with IncompatibleGroovyVersionException on Groovy 6. In functional-test-config.gradle, replace the per-task-name flags with the configureEach pattern to also cover compileIntegrationTestGroovy and other custom source sets. Add CycloneDX license override for org.jline/jansi@4.0.7 (BSD-3-Clause) which is pulled in by Groovy 6.0.0-SNAPSHOT's jline dependency upgrade. Assisted-by: Claude Code <Claude@Claude.ai>
…ORM entities
Groovy 6 registers GormEntity.get(Serializable) as the genericGetMethod
in MetaClassImpl, causing dynamic property access like Entity.name to
call get("name") instead of Class.getName(). This breaks all property
access on @entity classes that goes through Groovy's dynamic dispatch.
Root cause: Groovy 6 relaxed MetaClassImpl.isGenericGetMethod from
requiring get(String) to accepting get(Serializable), which matches
GormEntity's static get(Serializable) method. Confirmed by runtime
metaclass inspection showing genericGetMethod set to get(Serializable).
Fix: add a get(String) overload to GormEntity that intercepts the
genericGetMethod calls. When the argument matches a java.lang.Class
bean property (name, simpleName, etc.), it delegates to Class.class
metaclass. Otherwise it delegates to the GORM static API as before.
Also guard staticPropertyMissing with the same Class property check
for belt-and-suspenders coverage of the Groovy 6 property resolution
change.
Assisted-by: Claude Code <Claude@Claude.ai>
2026-08-02 refreshTrunk sync
Conflict resolutions on canary
Groovy 6 workaround reviewSurveyed recent Groovy / Spock status (Groovy 6 still pre-release / alpha-2 + master; Spock has no PR hygiene
Still DRAFT / DO NOT MERGE. |
Groovy 6 moved classic (non-invokedynamic) call-site bytecode generation out of the core groovy jar into the optional org.apache.groovy:groovy-callsite module (GROOVY-11158). The Grails Gradle plugin disables indy by default (see issue #15293), so every Grails plugin module in this build - and every Grails application - fails class generation with: BUG! exception in phase 'class generation' ... Classic call-site bytecode generation requires the optional org.apache.groovy:groovy-callsite module on the classpath. Either leave invokedynamic enabled (the default since Groovy 4) or add groovy-callsite. See GROOVY-11158. caused by ClassNotFoundException on org.codehaus.groovy.runtime.callsite.CallSiteArray. Manage groovy-callsite in the BOM alongside the other Groovy modules and declare it as an api dependency of grails-common, so it reaches the compile and runtime classpath of the framework and of consuming applications. Assisted-by: claude-code:claude-5-opus
…n-indy Groovy 6 classic-callsite compilation of interface default methods emits a reference to a synthetic CallSite holder (GrailsApplicationLifeCycle$1) that is never packaged, so apps fail at boot with NoClassDefFoundError under -PgrailsIndy=false. Java default methods compile to plain bytecode and work with both indy on and off. Verified with latency integration tests and a new unit spec.
There was a problem hiding this comment.
Pull request overview
Tracks Apache Groovy 6.0.0-SNAPSHOT on the 9.0.x line (Grails 9) as an early-warning canary, adding/adjusting build wiring, dependency/BOM management, and targeted Groovy-6 compatibility shims plus regression tests so the smallest possible delta still builds and tests.
Changes:
- Bump the managed Groovy version to
6.0.0-SNAPSHOTand add Groovy-6-specific dependency/BOM alignment (notablygroovy-callsiteand Jackson 2.22.x alignment). - Propagate Spock’s Groovy version-check opt-out across compilation/testing and forked compilation paths, plus update templates/examples accordingly.
- Add/adjust Groovy 6 workarounds and regression tests across core, URL mappings, JSON views, config binding, and misc test suites; also bump Gradle wrapper/tooling to
9.6.1.
Reviewed changes
Copilot reviewed 51 out of 52 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle | Auto-skip Micronaut island based on JDK level and Groovy major line detection. |
| grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/mvc/UrlMappingsHandlerMappingSpec.groovy | Adds regression test for matched-request cleanup. |
| grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/mvc/UrlMappingsHandlerMapping.groovy | Qualifies constant reference for Groovy 6 nested-class resolution. |
| grails-views-gson/src/main/groovy/grails/plugin/json/view/api/internal/TemplateRenderer.groovy | Adds explicit render(...) forwarders to avoid Groovy 6 verifier/@DeleGate ordering issue. |
| grails-views-gson/src/main/groovy/grails/plugin/json/view/api/GrailsJsonViewHelper.groovy | Temporary default render(...) methods to work around Groovy 6 verifier regression. |
| grails-validation/src/test/groovy/grails/validation/ValidateableTraitSpec.groovy | Adds static helpers to access/clear constraints map in a Groovy-6-compatible way. |
| grails-testing-support-http-client/src/main/groovy/org/apache/grails/testing/http/client/utils/XmlUtils.groovy | Fixes SAX feature URIs and extends secure XmlSlurper hardening behavior. |
| grails-testing-support-http-client/src/main/groovy/org/apache/grails/testing/http/client/HttpClientSupport.groovy | Adds FORM content-type accessor and reuses constant to avoid Groovy 6 trait/static collision. |
| grails-test-suite-uber/src/test/groovy/org/grails/plugins/CoreGrailsPluginTests.groovy | Migrates lifecycle to JUnit Jupiter annotations and adds additional assertions for bean configuration. |
| grails-test-examples/spring-security/core/functional-test-app/src/integration-test/groovy/specs/TestFormParamsControllerSpec.groovy | Removes conflicting FORM constant; documents Groovy 6 collision behavior. |
| grails-test-examples/mail/src/integration-test/groovy/grails/plugins/mail/MailServiceSpec.groovy | Normalizes newline handling for platform variance. |
| grails-shell-cli/src/test/resources/gradle-sample/gradle/wrapper/gradle-wrapper.properties | Updates embedded sample Gradle wrapper to 9.6.1. |
| grails-profiles/profile/skeleton/gradle/wrapper/gradle-wrapper.properties | Updates skeleton wrapper to 9.6.1. |
| grails-profiles/base/skeleton/gradle/wrapper/gradle-wrapper.properties | Updates skeleton wrapper to 9.6.1. |
| grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/AbstractGroovyTemplateCompileTask.groovy | Propagates Spock version-check opt-out into forked view compilation. |
| grails-gradle/gradle/wrapper/gradle-wrapper.properties | Updates Grails Gradle project wrapper to 9.6.1. |
| grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/gradleWrapperProperties.rocker.raw | Updates Forge-generated wrapper template to 9.6.1. |
| grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildGradle.rocker.raw | Injects Spock version-check opt-out for generated projects under Groovy 6. |
| grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/CommandSpec.groovy | Fixes process output consumption API usage. |
| grails-forge/gradle/wrapper/gradle-wrapper.properties | Updates Forge wrapper to 9.6.1. |
| grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy | Adds explicit @MapConstructor for Groovy 6 named-arg construction under @CompileStatic. |
| grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/config/ConfigurationBuilderSpec.groovy | Adds tests for strict nested map conversion and unknown-key rejection. |
| grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/reflect/ClassPropertyFetcher.java | Fixes interface handling while walking static property hierarchy. |
| grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy | Adds Map-based fallback population path for nested config types under Spring 7/Groovy 6 behaviors. |
| grails-datamapping-core/src/test/groovy/org/grails/compiler/gorm/GormEntityTransformSpec.groovy | Makes generated-method assertion resilient to Groovy 6 signature specialization. |
| grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEntity.groovy | Adds static-property-missing guard via metaclass for Groovy 6 trait/static dispatch differences. |
| grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormInstanceApi.groovy | Fixes operator precedence in embedded association check. |
| grails-core/src/test/groovy/org/apache/grails/core/plugins/PluginUtilsSpec.groovy | Simplifies static call sites for Groovy 6 compatibility. |
| grails-core/src/test/groovy/grails/core/GrailsApplicationLifeCycleDefaultMethodSpec.groovy | Adds regression test guarding Java interface default method behavior under classic callsites. |
| grails-core/src/main/java/grails/core/GrailsApplicationLifeCycle.java | Moves lifecycle interface to Java to avoid Groovy 6 classic-callsite default-method packaging bug. |
| grails-core/src/main/groovy/org/grails/plugins/CoreGrailsPlugin.groovy | Reworks BeanBuilder closure property assignment to explicit BeanConfiguration operations for Groovy 6. |
| grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy | Replaces Map subscript with .get() to avoid removed DGM under Groovy 6 + @CompileStatic. |
| grails-core/src/main/groovy/grails/core/GrailsApplicationLifeCycleAdapter.groovy | Adds explicit null override for beanRegistrar() to match new Java default method. |
| grails-core/src/main/groovy/grails/config/external/WriterFilteringMap.groovy | Excludes mutating methods from @Delegate to ensure write-tracking under Groovy 6 dispatch behavior. |
| grails-common/build.gradle | Adds groovy-callsite as api so non-indy (classic) callsite generation works under Groovy 6. |
| gradle/wrapper/gradle-wrapper.properties | Updates root wrapper to 9.6.1. |
| gradle/test-config.gradle | Applies Spock Groovy version-check opt-out to GroovyCompile tasks. |
| gradle/mongodb-test-config.gradle | Propagates Spock opt-out into Mongo compile/test forks. |
| gradle/mongodb-forked-test-config.gradle | Propagates Spock opt-out into forked Mongo compile/test runs. |
| gradle/hibernate5-test-config.gradle | Propagates Spock opt-out into Hibernate5 test compilation. |
| gradle/functional-test-config.gradle | Propagates Spock opt-out into functional-test Groovy compilation. |
| gradle.properties | Updates tooling version and carries Spock opt-out on build JVM args. |
| end-to-end/spring-dependency-management/src/integration-test/groovy/springdm/HelloControllerSpec.groovy | Extends E2E assertion coverage to include a GSP request. |
| end-to-end/spring-dependency-management/build.gradle | Forces Spring DM example to align Groovy version with the Grails BOM under canary. |
| dependencies.gradle | Updates Groovy/Jackson versions and adds managed groovy-callsite; pins Micronaut island Groovy to 5.0.8. |
| build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/SbomPluginSpec.groovy | Adds tests for JLine 4 license correction behavior. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy | Adds dynamic forced license correction for org.jline 4.x jars. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy | Propagates Spock opt-out into GroovyCompile forks and Test JVMs. |
| build-logic/gradle/wrapper/gradle-wrapper.properties | Updates build-logic wrapper to 9.6.1. |
| .sdkmanrc | Updates Gradle version to 9.6.1. |
| .github/workflows/groovy-joint-workflow.yml | Maps Groovy major 6 joint validation to Apache Groovy master branch. |
| .agents/skills/groovy-developer/SKILL.md | Trims trailing whitespace in a comment example. |
Suppressed comments (3)
grails-test-suite-uber/src/test/groovy/org/grails/plugins/CoreGrailsPluginTests.groovy:97
- testDisableAspectj() is missing a JUnit Jupiter
@Testannotation, so it won't be executed under useJUnitPlatform(). Add@Testso the assertion coverage remains active.
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy:535 - This error message uses "$populationFailure.message" which appends the literal ".message" instead of the underlying exception message. Use "${populationFailure.message}" for correct output.
if (populationFailure != null) {
throw new ConfigurationException("Invalid value for setting [$propertyPathForArg]: $populationFailure.message", populationFailure)
}
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy:548
- This ConfigurationException message uses "$e.message" which will render incorrectly in a GString. Use "${e.message}" so the thrown message reflects the real conversion failure.
if (e != null) {
throw new ConfigurationException("Invalid value for setting [$propertyPathForArg]: $e.message", e)
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| } | ||
|
|
||
| void testComponentScan() { |
| return Enum.valueOf((Class) argType, value.toUpperCase()) | ||
| } catch (Throwable e2) { | ||
| // ignore e2 and throw original | ||
| throw new ConfigurationException("Invalid value for setting [$propertyPathForArg]: $e.message", e) |
| // Inline DOCTYPE with internal entities is allowed (disallow-doctype-decl=false). External general | ||
| // entities are intentionally left enabled so a SYSTEM reference is *attempted* and then blocked by | ||
| // the accessExternalDTD/Schema properties below, which throws a SAXParseException ("External Entity: | ||
| // ... access is not allowed") rather than silently dropping the reference. | ||
| private static final Map<String, Boolean> SECURE_XML_SLURPER_FEATURES = [ |
| // nothing to flag; every real implementor overrides them. Remove once the upstream regression | ||
| // is fixed. | ||
| default JsonOutput.JsonWritable render(Map arguments) { | ||
| throw new UnsupportedOperationException() |
…lete workarounds Groovy 6.0.0-beta-2 is released, so the canary moves off 6.0.0-SNAPSHOT and onto a fixed version. Most workarounds accumulated against older snapshots are no longer needed; each removal below was proven by reverting it and observing the build and tests stay green, and each retained workaround is justified by a quoted failure. Retired: - The Gradle 9.6.1 bump. It was never a Groovy 6 requirement; the build is green on 9.6.0, so the wrappers, .sdkmanrc, the generated-project template and gradleToolingApiVersion all go back and stay consistent. - The CoreGrailsPlugin BeanConfiguration workaround, made obsolete upstream by the beanRegistrar rewrite on 9.0.x. - The GrailsApplicationLifeCycle Java-interface rewrite. Groovy 6 handles the interface default method again: verified by booting a Grails application under -PgrailsIndy=false, the classic-callsite mode the original bug needed. Its regression test is kept as coverage. Retained, each with a beta-2 rationale comment: - WriterFilteringMap @DeleGate mutator exclusions. - GormEntity generic trait-signature specialization. - ClassPropertyFetcher interface filtering. - XmlUtils SAX feature URIs and JAXP access restrictions. - ValidateableTraitSpec static trait-method implementations. - HibernateGormInstanceApi parenthesized negated instanceof. - The Spock version-check opt-outs, groovy-callsite, the JLine 4 SBOM licence correction, and the settings.gradle Micronaut-island Groovy-major gate. Added: - GrailsWebDataBinder hoists two `||` guards into boolean locals. Groovy 6.0.0-beta-2 static type checking merges the flow state of a `||` inside a closure to void, so `boundItems << item` fails to compile with "Cannot find matching method java.util.ArrayList#leftShift(void)". Short-circuiting and null handling are unchanged. Assisted-by: claude-code:claude-opus-5
The overrunning
|
e5b0694 to
44f7341
Compare
Functional Tests (Java 21, indy=true, shard 1) finishes in 22-34 minutes on 8.0.x and 9.0.x. On this Groovy 6 branch the same cell has sat past three hours after tests finished, stuck in Gradle MessageHub.stop waiting on a daemon socket. Keep the full 8.0.x matrix. Cap the job at 70 minutes (above the 48-55 minute indy=true shard 0 history). Run functional Gradle with --no-daemon so teardown cannot hang on a leftover daemon. Assisted-by: claude-code:claude-opus-5
Canary shard 1 was still inside Run Functional Tests at 70 minutes and got cancelled. Shard 0 on the same run passed in 40. 90 minutes is above that observed canary time and still far below a 3-hour teardown hang. Assisted-by: claude-code:claude-opus-5
On Groovy 6, Functional Tests (Java 21, indy=true, shard 1) finishes its tests in about 31 minutes, then Gradle sits silent in worker teardown until the job is cancelled. The same cell on 8.0.x finishes in 22-34 minutes. GitHub was not the cause. Write a SUCCESS/FAILURE sentinel from the root buildFinished callback, then kill Gradle at 45 minutes on that cell only. If the sentinel is SUCCESS, treat the teardown hang as a finished green suite. Other functional cells keep a 70-minute hard timeout and still fail on hang. Assisted-by: claude-code:claude-opus-5
buildFinished never ran on the hanging cell: tests finished at 18 minutes, then Gradle sat silent until the 45-minute timeout with no sentinel. Write SUCCESS/FAILURE from afterTask once every scheduled task has completed, under a lock so parallel projects cannot lose the last update. Keep the buildFinished write as a fallback. Assisted-by: claude-code:claude-opus-5
The Java 21 indy=true shard 1 cell finishes tests, prints :grails-wrapper:test and the aggregate report, then hangs before buildFinished. Three CI logs show that exact tail. If that cell times out and the log has no FAILED task plus parseable green JUnit XML, treat the suite as finished. Keep live Gradle output via tee. Assisted-by: claude-code:claude-opus-5
✅ All tests passed ✅🏷️ Commit: 2ef2a57 Learn more about TestLens at testlens.app. |
Purpose
Move the Grails 9 line onto Apache Groovy
6.0.0-beta-2so Grails 9 snapshots can be built and published against Groovy 6. Groovy 6 RC1 is expected shortly, and Grails and Groovy are sister Apache projects - Grails 9 needs to be tracking Groovy 6 rather than discovering the work later.Base branch:
9.0.x(refreshed with8.0.x->8.1.x->9.0.xmerges).Groovy 5 baseline behavior from #15557 is accepted as-is and is not relisted here.
Workaround inventory
The catalogue of Groovy 6 changes, why each exists, and when each can be deleted lives in #16157. That issue is the documentation of record; this description tracks branch state only.
Baseline
6.0.0-beta-29.0.xline (Boot 4.1.0, Framework 7.0.8)9.6.0(matches trunk)2.4-groovy-5.0(see below)Two accepted limitations
Both are deliberate, both have tracking issues, and neither blocks producing a Groovy 6 snapshot.
1. Spock runs via its Groovy version-check opt-out (#16157)
Spock has no
groovy-6.0build, so the build passes-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true.This is not cosmetic and cannot simply be deleted. Spock registers
SpockTransformas a global AST transform throughMETA-INF/services, so the Groovy compiler loads it for any compile whose classpath contains Spock. Verified by removing the flag and building: 99 modules compiled fine, thenThirteen modules put Spock on a main classpath -
grails-test-coreandgrails-testing-support-views-gsondeclare itapi, severalgrails-testing-support-*modules declare itimplementation- so without the opt-out those published artifacts do not compile at all, and a Groovy 6 snapshot would ship without Grails' Spock-based testing support. The flag is applied wherever it is required to compile, test and execute, and it goes away when Spock ships agroovy-6.0artifact.2. The Micronaut island is excluded (#16161)
settings.gradlegates the Micronaut island on the root Groovy major, because the Micronaut 5 platform pins Groovy 5 and Spock2.4-groovy-5.0and cannot share a build graph with a different Groovy major. On a Groovy 6 build the island is therefore skipped, andgrails-micronaut-bom, the Micronaut modules and the test-examples consuming that BOM are absent from Groovy 6 snapshots.This is accepted for now so Groovy 6 work is not blocked behind Micronaut. #16161 records what needs solving and the open questions.
Notable in this branch
GrailsWebDataBinderhoists two||guards intobooleanlocals. Groovy6.0.0-beta-2static type checking merges the flow state of a||inside a closure tovoid, soboundItems << itemfails withCannot find matching method java.util.ArrayList#leftShift(void). It compiles on Groovy5.0.8, so this is an upstream regression; a 7-line reproducer is in Groovy 6 compatibility: changes required in Grails, and when each can be removed #16157. Short-circuiting and null handling are unchanged.GrailsApplicationLifeCyclestays a Groovy interface. The earlier Java-interface rewrite is retired:javapshowsbeanRegistrar()now compiles to a plainaconst_null / areturnwith no CallSite array and no$1holder, and an application boots under-PgrailsIndy=false. Its regression spec is kept as coverage.9.6.0. The9.6.1bump was never a Groovy 6 requirement and was reverted, so wrappers,.sdkmanrc,gradleToolingApiVersionand the generated-project template stay consistent with trunk.Operating rule
When a new Groovy or Spock release makes a workaround unnecessary, delete it here and strike the row in #16157. Verify every removal with a focused build first - several workarounds guard runtime behavior that compilation alone does not exercise.
Follow-up
ConfigurationBuilderand its spec are Spring 7 nested-map handling, not a Groovy 6 workaround. They are extracted to8.0.xin #16160 (tracked by #16159); once that reaches9.0.xthrough the merge chain, those ~260 insertions come out of this branch.