[Canary] Grails 9 on Groovy 6.0.0-SNAPSHOT - #15558
Conversation
This comment has been minimized.
This comment has been minimized.
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>
Keep the Micronaut island out of the default build graph when the root build targets a different Groovy major, while keeping the Hibernate 5 Micronaut BOM aligned to the island's Groovy 5 dependency set. Assisted-by: opencode:openai/gpt-5.5
Avoid static-context getClass() package lookups in the global Grails transform and expose the form content type from HttpClientSupport for merge-ref integration specs. Assisted-by: Hephaestus:gpt-5.5
Wait for the Process wrapper to observe termination before asserting liveness after RunningApplicationProcess.stop reports STOPPED. This avoids a Windows CI race where ProcessHandle termination completed before Process.isAlive() observed the exit. Assisted-by: opencode:gpt-5.5
Bring the Groovy 6.0.0-SNAPSHOT canary up to date with 8.0.x. Conflict resolution: - dependencies.gradle: keep groovy.version=6.0.0-SNAPSHOT and the grails-hibernate5-micronaut-bom Groovy 5 pin; adopt 8.0.x's Jackson management (jackson2 2.21.5 / jackson3 3.1.5 security overrides) and drop the canary's stale jackson 2.22.0 pins and jacksonBomDependencies block. This also clears the validateDependencyVersions failure, since 8.0.x now BOM-manages commons-codec at 1.22.0 (matching the transitive resolution). - RunningApplicationProcessSpec: take 8.0.x's Windows-reaper-aware stop assertion. Assisted-by: claude-code:claude-opus-4-8
Address failures surfaced after merging 8.0.x into the Groovy 6.0.0-SNAPSHOT canary: - dependencies.gradle: manage the com.fasterxml Jackson 2 suite at 2.22.0. groovy-yaml 6.0.0-SNAPSHOT transitively pulls jackson-dataformat-yaml (dragging core/databind/datatype-jsr310) at 2.22.0 and jackson-annotations at 2.22, so the BOM must manage each at >= the resolved version (rule 14). 2.22.0 is newer than the security-pinned 2.21.5 so it keeps the CVE-2026-54515 fix. Clears the validateDependencyVersions failure. - GlobalGrailsClassInjectorTransformation: call cache.get(key) instead of the cache[key] subscript. Under @CompileStatic the subscript binds to DefaultGroovyMethods.getAt(Map, String), removed in Groovy 6, so the AST transform threw NoSuchMethodError while compiling every artefact - the root cause cascading across the spring-security, redis and hibernate7 build jobs. The withDefault wrapper overrides get(), preserving the lazy injector lookup. - TestFormParamsControllerSpec: drop the redundant static FORM constant; it now inherits getFORM() from the HttpClientSupport trait. Groovy 6 rejects a static getFORM() alongside the trait's instance getFORM(). Assisted-by: claude-code:claude-opus-4-8
Replace version-specific JLine 4 license overrides with a constrained Maven JAR PURL matcher and cover its scope with regression tests. Assisted-by: opencode:gpt-5.6-sol
Use BeanConfiguration APIs for core bean properties and parent metadata so registration remains reliable under Groovy 6. Assisted-by: opencode:gpt-5.6-sol
Avoid closure delegation during static helper assertions so the existing include and exclude coverage executes reliably with Groovy 6. Assisted-by: opencode:gpt-5.6-sol
Qualify the outer request-attribute constant for Groovy 6 and cover cleanup through the public handler-chain lifecycle. Assisted-by: opencode:gpt-5.6-sol
Canonicalize CRLF pairs before checking rendered GSP text so the newline contract is tested consistently across platforms. Assisted-by: opencode:gpt-5.6-sol
Bring the Groovy 6 canary branch to the current 8.0.x tip while preserving canary-specific compatibility fixes. Assisted-by: opencode:gpt-5.6-sol
Fail closed when Spring cannot convert nested settings maps so typos no longer silently apply defaults, and cover the regression with a focused test. Assisted-by: opencode:gpt-5.6-sol
Keep the Groovy 6 canary BOM ahead of the Jackson 2.22.1 suite resolved through groovy-yaml and hibernate7-dbmigration so validateDependencyVersions stays green. Assisted-by: opencode:gpt-5.6-sol
Move every synced wrapper, tooling API pin, sdkmanrc entry, and Forge template to the current Gradle 9.6.1 release. Assisted-by: opencode:gpt-5.6-sol
Override Spring Boot's imported Groovy version property so the canary fixture runs with the same Groovy version used to compile the framework. Extend the functional test to cover both controller and GSP responses. Assisted-by: opencode:gpt-5.6-sol codegraph
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.
🚨 TestLens detected 1 failed test 🚨Here is what you can do:
Test SummaryCI / Functional Tests (Java 21, indy=true) > :grails-test-examples-app1:integrationTest
🏷️ Commit: 20e8ec7 Test FailuresRedirectWithAndWithoutParamsFunctionalSpec > Params are not added to the url after a redirect even if they are passed to the redirect (:grails-test-examples-app1:integrationTest in CI / Functional Tests (Java 21, indy=true))Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app. |
Purpose
Canary / DRAFT - DO NOT MERGE.
Track Apache Groovy
6.0.0-SNAPSHOTon Grails 9 while Groovy 6 is still pre-release. Early-warning only. Grails 9 is the intended Groovy 6 vehicle. Success metric: the smallest tip-vs-9.0.xdelta that still builds and tests.Groovy 5 baseline behavior from #15557 is accepted as-is and is not listed here.
Base branch:
9.0.x(refreshed 2026-08-03 with8.0.x→8.1.x→9.0.xmerges).Baseline (current tip)
6.0.0-SNAPSHOT2.4-groovy-5.0+ temporary version-check bridge9.0.xline9.6.1Remaining Groovy 6 workarounds (current tip)
Prefer deleting a row when upstream removes the need for it. Do not grow this list with unrelated trunk work.
-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=truethrough build JVM args,GroovyCompile/Testforks, Mongo/Hibernate5/functional test configs, forked GSP/GSON view compilation, and Forge-generatedbuild.gradle.5.0.8.2.22.1(annotations independently at2.22) so the BOM stays>=the suite pulled bygroovy-yaml/ Hibernate7 dbmigration.9.0.xpin (2.21.5).9.0.xalready manages the same line.GrailsJsonViewHelper.render(...)methods are temporarydefaultmethods;TemplateRendererhas explicitrenderforwarders instead of relying only on@Delegate.@Delegate/ interface methods. Candidate for retest against current Groovy master (GROOVY-10687), not removed without canary proof.@MapConstructoronBeanPropertyAccessorImpl.@CompileStatic, Groovy 6 no longer gets the named-arg constructor from@Canonicalalone.@Delegate(excludes = ['put', 'putAll', 'remove'])onWriterFilteringMap.GormEntity.staticPropertyMissingchecks the classMetaClassbefore falling through to the static API.BeanConfiguration.addProperty/setParentinstead of dynamic property assignment inCoreGrailsPluginBeanBuilder closures.UrlMappingsHandlerMapping.MATCHED_REQUESTin the nested handler.classInjectorCache.get(key)over subscript inGlobalGrailsClassInjectorTransformation(retained through the 9.0.x transform rewrite).@CompileStaticsubscript binds to a DGM method removed in Groovy 6..getrewrite.org.jline:*@4.xMaven jar PURLs.6joint validation to Apache Groovymaster.GROOVY_6_0_Xmaintenance branch yet; alpha-2 is already behind master.GROOVY_6_0_Xline, or joint validation learns the same rule on trunk.org.apache.groovy:groovy-callsitein the BOM and declare itapiongrails-common.GROOVY-11158), and the Grails Gradle plugin disables indy by default (#15293), so class generation aborts withClassNotFoundException: org.codehaus.groovy.runtime.callsite.CallSiteArraywithout it.indyto enabled. Note: the dependency is Groovy 6-only - gate it by Groovy major if this ever moves to a branch that still builds on Groovy 5.GrailsApplicationLifeCyclefrom Groovy to Java sodefault BeanRegistrar beanRegistrar()is a plain JVM default method; keep an explicit null override onGrailsApplicationLifeCycleAdapter.invokestatic GrailsApplicationLifeCycle$1.$getCallSiteArraybut never packagesGrailsApplicationLifeCycle$1, so real apps fail at boot withNoClassDefFoundError: grails/core/GrailsApplicationLifeCycle$1when the framework is built with-PgrailsIndy=false(the Grails default). Indy compilation of the same method is a plainreturn nulland boots fine. Confirmed by javap +grails-test-examples-latencyintegration tests. Related toGROOVY-11158/ interface-default + classic call-site codegen.GrailsJsonViewHelperdefaults (@CompileStatic),IContainerGebConfigurationdefaults (testFixtures).Not counted as workarounds
Present in the tip delta but treated as ordinary canary baseline, fixes, or tests:
groovy.versionto6.0.0-SNAPSHOT9.6.1upgrade (trunk is on9.6.0)ConfigurationBuildernested-map conversion / unknown-key rejectionClassPropertyFetcherinterface-entry static property hierarchy fixHibernateGormInstanceApiembedded association check (if still tip-only)ext['groovy.version']override so Spring DM does not pull Boot's Groovy over the canary BOMGrailsApplicationLifeCycleDefaultMethodSpec(guards This is a unit test for GRAILS-4200 #14)Operating rule
If a new Groovy 6 snapshot or Spock release makes a row unnecessary, delete that workaround in this PR. Do not grow the list with unrelated trunk work. Prefer verifying removal with a focused canary build before deleting.
Tip delta size (vs
9.0.x)Roughly ~50 files as of 2026-08-03 after the trunk refresh, the
groovy-callsitefix, and the lifecycle Java default-method fix. Keep shrinking this number.Local indy vs classic callsite notes (2026-08-03)
Framework JMH hot-path benches (separate experimental branch, technique from #16071) showed no clear indy regressions vs classic callsites on Groovy 6 for databinding/GSP/urlmappings, with interceptor matching faster under indy.
App-level validation is more important: with #14 unfixed, apps could not boot at all under non-indy. After the Java lifecycle default,
grails-test-examples-latencyboots and serves under both-PgrailsIndy=falseandtrue. That makes classic-callsite A/B possible again for real apps once the harness lands.