Skip to content

Groovy 5.0.x support for Grails 8 + Spring Boot 4#15557

Open
jamesfredley wants to merge 169 commits into
8.0.xfrom
grails8-groovy5-sb4
Open

Groovy 5.0.x support for Grails 8 + Spring Boot 4#15557
jamesfredley wants to merge 169 commits into
8.0.xfrom
grails8-groovy5-sb4

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Adds Apache Groovy 5.0.x support on top of 8.0.x, tracking 5.0.7-SNAPSHOT from GROOVY_5_0_X. Verified on JDK 21 under both indy=true and -PgrailsIndy=false.

Most of the diff is adapting to Groovy 5's stricter @CompileStatic type checking and a few API/behaviour changes (MetaClassHelper.capitalize removal, File truthiness, etc.). The areas worth a closer review are below.

Target stack

Component Version
Apache Groovy 5.0.7-SNAPSHOT (GROOVY_5_0_X)
Spock 2.4-groovy-5.0
Spring Boot 4.1.0
Spring Framework 7.0.x
Gradle 9.5.1
Jakarta EE 10
JDK 21+

AST-level fixes

  • VariableScopeVisitor NPE. Groovy 5's VariableScopeVisitor.visitConstructorOrMethod reads the method exceptions array without a null check, so it NPEs on methods created via ClassNode.addMethod(..., null, ...). Per review feedback that the correct fix is to populate the exceptions at the creation site rather than post-process them, Grails AST transforms now construct those method nodes with ClassNode.EMPTY_ARRAY instead of null (ServiceTransformation, ResourceTransform). A defensive proxy-MethodNode fallback remains at the processVariableScopes chokepoints (GrailsASTUtils, AstUtils) for the remaining nodes (e.g. the renamed node from AbstractMethodDecoratingTransformation) and any foreign null-exception methods we do not create. Reproducer: groovy5-variablescope-canonicalization-bug.
  • setVariableScope(new VariableScope()) in ResourceTransform / AbstractMethodDecoratingTransformation fixes a synthesised-ClosureExpression null-scope ClosureWriter NPE. This is an all-version fix (it also reproduces on Groovy 4.0.31), not Groovy-5-specific.
  • Logging transform. LoggingTransformer again drives Groovy's own LogASTTransformation to inject the log field (rather than hand-building the LoggerFactory.getLogger(...) field), so Grails follows the standard Groovy logging-transform path.

Workarounds for open upstream Groovy bugs

Each works around an unfixed Groovy 5 defect (verified still failing on 5.0.7-SNAPSHOT) and can be removed once the upstream fix lands in GROOVY_5_0_X.

# Site Upstream bug Status Reproducer
1 ConstrainedProperty.DEFAULT_MESSAGES is built from a Groovy map literal (then wrapped immutable) instead of an anonymous-HashMap-with-instance-initialiser On Groovy 5 the bareword references to the sibling DEFAULT_*_MESSAGE constants inside such an initialiser compile to dynamic getProperty reads on this (the HashMap); because the receiver is a Map, the MOP resolves them as key lookups on the still-empty map, so every entry was captured as null. A map literal resolves the constants against the enclosing interface scope and is correct on every version. GROOVY-12063 (open) in-repo DefaultMessageResolutionSpec
2 Validateable.resolveDefaultNullable() / BeanPropertyAccessorFactory use Method.invoke reflection TraitReceiverTransformer rewrites the in-trait this.defaultNullable() call to a direct trait-helper static call, losing the implementing-class override; reflection is the only path that honours it. GROOVY-11985 (open) repro

Removed since the previous description. The ControllerActionTransformer OptimizingStatementWriter.ClassNodeSkip workaround for the indy=false parameterized-controller-action MissingPropertyException has been dropped: GROOVY-12062 is now Resolved/Fixed (fix-version 5.0.7) and verified on the consumed 5.0.7-SNAPSHOT. The ClassNodeSkip tag and the boot4-disabled-integration-test-config.gradle exclusions are gone, and the affected grails-test-examples integration tests run under both indy modes.

The g.taglib STC change (GroovyPageTypeCheckingExtension) matches the taglib namespace by name in methodNotFound - the approach @paulk-asert recommended for GROOVY-12041 (open) - because when the receiver inherits getProperty(String) the STC unresolvedVariable/unresolvedProperty callbacks never fire. Static compilation of g.* taglib calls works; the only loss is the negative (undeclared-variable) check - see the table below. Reproducer: groovy5-stc-extension-node-identity-bug.

Worked around without a clean fix (flagged in review)

Items @jdaugherty flagged where a clean fix was not available, so the Groovy 5 behaviour is worked around. These are deliberately left as-is with the rationale below; none has a published upstream fix.

Site Concern (review) Current handling Upstream
GspCompileStaticSpec undeclared-variable specs "The whole point of @CompileStatic is to fail on unresolvable properties - this still seems like a regression." Two @IgnoreIf({ groovy5 }) skips on the negative (undeclared-variable) assertions only; g.* taglib resolution itself works via the name-matching extension. GROOVY-12041 (open); related GROOVY-6362 / GROOVY-11817
GrailsWebDataBinderSpec @Entity fixtures "If @Sortable + @Entity don't generate the same method, this is a Groovy bug we need to understand." Hand-written compareTo/Comparable replaces @Sortable in the two test fixtures (the combination NPEs in canonicalization). No ticket filed
ClassPropertyFetcherTests.TestTrait "from is public and should be copied to implementers - why does it fail only with Serializable? Likely a Groovy bug." Trait type bound relaxed from <F extends Serializable> to <T>; the domain still implements Serializable, so the datastore behaviour under test is unchanged. No ticket filed
GrailsASTUtils / AstUtils processVariableScopes "Isn't the real fix to populate the exceptions at creation instead of post-processing?" Done at the Grails creation sites (EMPTY_ARRAY); a proxy-MethodNode fallback is retained only for foreign null-exception nodes we don't create. No ticket; Groovy could also harden addMethod to reject null
TransactionalTransformSpec "These tests exercised the generated code by direct invocation before." Direct $tt__ invocation assertions dropped; generation still verified via getDeclaredMethod signature checks. Spock 2.4 limitation (per-iteration context unavailable off the runner), not Groovy

Related PRs (target 8.0.x)

PR Title Status Relevance
#15727 Update Spring Boot 4.1 dependency versions Merged Brings the base to Spring Boot 4.1.0, consumed here via merge.
#15703 Update Spock + align test dependencies Merged Spock 2.4 alignment on the 8.0.x line.
#15467 Replace Spring Dependency Management with Gradle platform + BOM property overrides Merged Infrastructure for the BOM version-override conflicts (asm, javaparser); let the per-app overrides be dropped.
#15710 Enable SiteMesh 3 on the 8.x (Spring Boot 4) line Merged Removed the SiteMesh 3 / Spring 7 half of the old integration-test exclusions.
#15677 Bump Micronaut platform to 5.0.0 (GA) Open Resolves the org.ow2.asm 9.10.1-vs-9.9.1 BOM version conflict inherited from base.
#15669 @GrailsCompileStatic support for controllers that call tag libs Open Related to the g.taglib STC change.
#15652 Stop leaking build-only deps into the Grails BOM (DRAFT) Open Long-term home for the docs-only javaparser-core BOM entry.

matrei and others added 30 commits May 15, 2025 10:51
# Conflicts:
#	build.gradle
#	dependencies.gradle
#	grails-forge/build.gradle
#	grails-gradle/build.gradle
# Conflicts:
#	buildSrc/build.gradle
#	dependencies.gradle
#	grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy
#	grails-gradle/buildSrc/build.gradle
# Conflicts:
#	dependencies.gradle
#	gradle/test-config.gradle
#	grails-forge/settings.gradle
#	settings.gradle
# Conflicts:
#	gradle.properties
#	grails-core/src/test/groovy/org/grails/plugins/BinaryPluginSpec.groovy
Cherry-picked comprehensive Groovy 5 compat from 9574fe8.

Conflict resolutions:
- dependencies.gradle: Groovy 5.0.5 GA (not SNAPSHOT) + Jackson 2.21.2
- LoggingTransformer: Keep manual log field injection (avoids Groovy 5 VariableScopeVisitor NPE entirely)
- TransactionalTransformSpec: Remove direct Spock feature method invocation (Groovy 5/Spock 2.x incompatible)
- grails-test-core/build.gradle: Remove spock-core transitive=false, keep junit-platform-suite
- grails-test-suite-uber/build.gradle: Remove spock-core transitive=false and explicit byte-buddy
jamesfredley and others added 2 commits June 5, 2026 15:23
Removes workaround for Groovy 5 OptimizingStatementWriter slow-path
bug for parameterized controller actions.

Groovy issue https://issues.apache.org/jira/browse/GROOVY-12062 fixed by
apache/groovy#2590 upstream.
@matrei

matrei commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

GROOVY-12062, Controller-action parameter scope under indy=false, now fixed at the source, workaround removed.

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went over all of these changes, and these are the outstanding issues:

Comment thread dependencies.gradle Outdated
Comment thread dependencies.gradle Outdated
Comment thread grails-core/src/main/groovy/org/grails/compiler/injection/GrailsASTUtils.java Outdated
}

// Note: In Groovy 5, the type checking extension behavior changed and undeclared variables
// in GSP templates may not trigger compilation errors. This is a known limitation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the whole point of CompileStatic is to fail on unresolveable properties. This still seems like a regression. The taglib support is separate from this.

Use Groovy's LogASTTransformation again for the injected Slf4j logger so Grails follows the standard Groovy logging transform path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
Normalize generated MethodNode exception arrays to empty arrays so Groovy 5 VariableScopeVisitor never sees null exceptions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
Remove stale workaround comments and point the GSP Groovy 5 skip at the tracked upstream issue.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
Keep the validation message map immutable while preserving the GROOVY-12063 workaround and document the still-active trait and sortable regressions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
Check generated HibernateEntity methods on the declaring class and avoid environment-dependent Liquibase log output assertions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
Use Renderer<?> for default view renderer fallbacks so CompileStatic sees the generic renderer contract without changing erased constructor signatures.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Assisted-by: Hephaestus:openai/gpt-5.5 oracle
@jamesfredley

Copy link
Copy Markdown
Contributor Author

Pushed the latest cleanup as six focused commits through 308c451. Addressed and resolved the concrete review items for redundant comments, Hibernate generated-method checks, dbmigration log assertions, MethodNode exception arrays, DEFAULT_MESSAGES immutability, LoggingTransformer Slf4j handling, and the active Groovy 5 guard comments. I left two conversations open intentionally because they remain upstream/reviewer-decision topics rather than completed code changes: the Groovy 5 GSP unresolved-property STC regression and the Groovy 5 wildcard assignment question in DefaultViewRenderer.

@jamesfredley jamesfredley force-pushed the grails8-groovy5-sb4 branch from 308c451 to 7c1bae7 Compare June 11, 2026 11:15
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
@jamesfredley

Copy link
Copy Markdown
Contributor Author

Last-24h update: review burn-down + base alignment

Description refreshed to match the current tree. Quick map of the commits since yesterday and the review threads each one closes out.

Base alignment (via merge)

Review feedback addressed

Commit Change Closes
2b83d84 LoggingTransformer drives Groovy's own LogASTTransformation again instead of hand-building the log field "retest this assumption, we want the @Slf4j path" / "ordering needs to run before the Slf4j AST"
37a9a4d Generated method nodes are created with ClassNode.EMPTY_ARRAY exceptions at the creation sites (ServiceTransformation, ResourceTransform); proxy fallback kept only for foreign null-exception nodes "isn't the real fix to populate the exceptions at creation?"
7c1bae7 Default view renderer fallbacks typed Renderer<?> (no erased-signature change) "why isn't ? allowed for Object?" on DefaultViewRenderer
dc37429 HibernateEntityTraitGeneratedSpec checks the declaring class and fixes the findWithSql/findAllWithSql duplicate; GroovyChangeLogSpec drops env-dependent Liquibase stdout assertions "this is a bug - findAllWithSql was changed to findWithSql" / "isn't this fixed now?"
c3eba98 DEFAULT_MESSAGES kept immutable (unmodifiableMap) while preserving the GROOVY-12063 map-literal workaround; trait + @Sortable regressions documented inline "maps are LinkedHashMaps - is this meant to be immutable?" + the two "this is a Groovy bug" threads
1eb7022 Removed redundant comments (dependencies.gradle, GraphQL spec) and pointed the GSP skip at GROOVY-12041 "remove the redundant comment" x3
6f776b0 CodeQL no longer autobuilds the Groovy sample apps CI noise

Workaround removed (snapshot caught up)

  • GROOVY-12062 is now Resolved/Fixed (5.0.7) and verified on the consumed 5.0.7-SNAPSHOT, so the ControllerActionTransformer OptimizingStatementWriter.ClassNodeSkip tag and the boot4-disabled-integration-test-config.gradle exclusions were dropped (d3506cf). The indy=false parameterized-action integration tests now run in both indy modes. This row is gone from the description.

Still open (double-checked against the snapshot)

The two JIRA-tracked workarounds remain because both tickets are still Open and still reproduce on 5.0.7-SNAPSHOT:

  • GROOVY-12063 (open) - ConstrainedProperty.DEFAULT_MESSAGES.
  • GROOVY-11985 (open) - Validateable / BeanPropertyAccessorFactory reflection.

GROOVY-12041 (open) also still affects the g.taglib STC path. The remaining no-clean-fix items @jdaugherty flagged (disabled undeclared-variable GSP specs, @Sortable+@Entity fixtures, the TestTrait bound, the processVariableScopes proxy fallback, and the TransactionalTransformSpec direct-invocation assertions) are now collected in the new "Worked around without a clean fix" table in the description.

matrei and others added 9 commits June 11, 2026 16:55
- In Groovy 5, the exceptions parameter cannot be null.
- Use ClassNode.EMPTY_ARRAY and Parameter.EMPTY_ARRAY
consistently when creating methods.
Keep the Spring Dependency Management regression example aligned with the source tree's Groovy and Spock BOM properties when importing the Grails BOM. This prevents the example from resolving stale published snapshot metadata while local Grails modules are compiled against the current Groovy line.

Assisted-by: opencode:openai/gpt-5.5 oracle
Fixes a regression in `grails-fields` FormFieldsTagLib, where page-scope
bean names were being resolved through a binding map lookup that no longer
behaved the same with Groovy 5. The fix normalizes lookup keys to String
before accessing `pageScope.variables`, and a regression test was added
in `RequiredAttributesSpec`.
The previous change was the result of a problem with the
TestLens GitHub CI app/addon.

After this was resolved upstream, the change is no longer
necessary.
@matrei

matrei commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Groovy Jira ticket created for @Sortable and @Entity compilation failure:
https://issues.apache.org/jira/projects/GROOVY/issues/GROOVY-12089

@matrei

matrei commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Groovy Jira Ticket for "bounded generic trait property generates unimplemented abstract setter" issue in ClassPropertyFetcherTests:
https://issues.apache.org/jira/projects/GROOVY/issues/GROOVY-12091

@testlens-app

testlens-app Bot commented Jun 15, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 3842442
▶️ Tests: 41918 executed
⚪️ Checks: 43/43 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants