Skip to content

Document Grails 8 API compatibility breaks#15823

Open
jamesfredley wants to merge 7 commits into
8.0.xfrom
docs/grails-8-api-compat-notes
Open

Document Grails 8 API compatibility breaks#15823
jamesfredley wants to merge 7 commits into
8.0.xfrom
docs/grails-8-api-compat-notes

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR updates the Grails 8 upgrade guide to document the API and binary compatibility findings from the 8.0.x versus 7.1.2 japicmp follow-up review.

The japicmp pass compared 47 published Grails 7.1.2 module jars with locally built 8.0.0-SNAPSHOT jars. 33 modules had no binary-incompatible changes. The remaining findings were cross-checked against the existing Grails 8 upgrade guide so this PR could fill only the real documentation gaps and correct one inaccurate existing note.

This PR does two things:

  1. Corrects the enum-marshaller migration text in section 8.
  2. Adds a new section, 31. Extension API Compatibility Notes, for confirmed extension-level compatibility changes that were not already documented elsewhere in the guide.

Why this is needed

The existing Grails 8 upgrade guide already covered many broad migration areas, including Spring Boot 4, Jackson 3, TagLib test cleanup, method-based TagLib handlers, JSON view converter migration, SiteMesh compatibility, GORM nullability defaults, MIME defaults, and Accept-header behavior.

The japicmp review found two remaining documentation problems:

  • Several confirmed binary/API breaks were still undocumented, especially for plugin authors, custom Gradle integrations, shell extensions, and applications that directly import Grails extension types.
  • Section 8 contradicted the actual Grails 8 artifacts by saying the legacy JSON/XML enum marshaller classes remained available temporarily. They are already removed in Grails 8.

Row-by-row japicmp coverage

Module Japicmp finding PR disposition
grails-codecs CodecsGrailsPlugin.doWithSpring() removed Added to the new extension API compatibility section. The guide now says the codecs plugin no longer overrides that hook and custom plugins should register their own beans from their own doWithSpring() implementation.
grails-controllers ControllersAutoConfiguration.dispatcherServletRegistration(...) return type changed because of the Spring Boot 4 package move Added to the new section. The guide now points direct imports and overrides to Spring Boot 4's org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean.
grails-converters grails.web.JSONBuilder removed Added to the new section. The guide now points JSON builder code to groovy.json.JsonBuilder or groovy.json.StreamingJsonBuilder.
grails-converters org.grails.web.converters.marshaller.json.EnumMarshaller and org.grails.web.converters.marshaller.xml.EnumMarshaller removed Corrected section 8. The guide now states the legacy classes were removed in Grails 8, that SimpleEnumMarshaller is the default, and that there is no Grails 8 configuration switch to restore the verbose enum-object shape.
grails-core GrailsApplicationPostProcessor(...), ProfilingGrailsApplicationPostProcessor(...), and DefaultGrailsPluginManager(...) constructor and plugin-discovery wiring changed Existing Grails 7.1 upgrade notes already covered the plugin-discovery migration. This PR also adds a Grails 8 compatibility row: direct GrailsApplicationPostProcessor and DefaultGrailsPluginManager construction requires the plugin-discovery dependency, while ProfilingGrailsApplicationPostProcessor keeps its existing constructor shape but requires a grailsPluginDiscovery bean in the application context.
grails-core grails.plugins.PluginFilter family removed or moved Added to the new section. The guide now points to the org.apache.grails.core.plugins.filters package and names explicit replacement types without recommending wildcard imports.
grails-core grails.util.Mixin, MixinTargetAware, and MixinTransformation removed Added to the new section. The guide now recommends replacing legacy mixins with traits, extension methods, or explicit composition.
grails-core grails.validation.ConstraintsEvaluator removed Added to the new section. The guide now points to org.grails.datastore.gorm.validation.constraints.eval.ConstraintsEvaluator.
grails-core BinaryGrailsPluginDescriptor, CorePluginFinder, and BinaryGrailsPlugin.getBinaryDescriptor() removed Added to the new section. The guide now points to current plugin-discovery APIs and BinaryGrailsPlugin.getPluginDescriptor().
grails-domain-class ConstraintEvaluatorAdapter and GrailsDomainClassAutoConfiguration.constraintsEvaluator(...) removed Added to the new section. The guide now points direct integrations to the current validateableConstraintsEvaluator(...) bean path and datastore constraint evaluator APIs.
grails-fields BeanPropertyAccessor.getBeanClass() and setter, PropertyPathAccessor.getBeanClass(), FormFieldsTagLib.input(...), and direct render* helpers changed or removed Added to the new section. The guide now points to beanType and to the documented f:field, f:widget, and f:displayWidget tags/templates instead of direct helper calls.
grails-gradle-plugins ApplicationContextCommandTask, ApplicationContextScriptTask, ProfileCompilerTask, and GrailsRunTask are abstract; GrailsGradlePlugin.createBuildPropertiesTask(...) return type changed; IntegrationTestGradlePlugin.get* constants removed Added to the new section. The guide now tells custom Gradle integrations to use Gradle task registration, source sets, configurations, and current test-phase APIs.
grails-gsp Public tag closure accessors/overloads removed from ApplicationTagLib, CountryTagLib, FormatTagLib, and FormTagLib; helper methods made private Already covered at the behavior level in section 22, Method-based TagLib Handlers. This PR does not duplicate that section.
grails-shell-cli AetherGrapeEngine and AetherGrapeEngineFactory removed Added to the new section. The guide now points legacy shell resolver extensions to MavenResolverGrapeEngine and MavenResolverGrapeEngineFactory.
grails-shell-cli JLine 2 complete(...) signatures moved to JLine 3 Added to the new section. The guide now points completers to the JLine 3 org.jline.reader.Completer contract.
grails-sitemesh3 GrailsLayoutHandlerMapping, RenderSitemeshTagLib members, and Sitemesh3GrailsPlugin lifecycle changes Already covered by section 23, Known Plugin Incompatibilities, which tells applications using grails-sitemesh3 to stay on the grails-layout plugin until compatibility is available. This PR does not duplicate that warning.
grails-spring GrailsApplicationContext.getTheme(String) and onRefresh() removed Theme removal was already covered in section 10. This PR adds onRefresh() to the new section and directs custom context subclasses to supported Spring lifecycle extension points.
grails-testing-support-web GrailsWebUnitTest.getPurgeTagLibMetaClass() and TagLibUnitTest.getPurgeTagLibMetaClass() removed Already covered in section 21, Tag Library Test Cleanup Changes. This PR does not duplicate that section.
grails-views-gson grails.plugin.json.builder.* removed and helper APIs moved to groovy.json.* Already covered in section 24, Custom JSON View Converters. This PR does not duplicate that section.

What changed in the guide

Section 8: enum serialization

The guide previously said:

If you need to restore the legacy behavior temporarily, the deprecated marshallers remain available but will be removed in a future release.

That was incorrect for Grails 8. The guide now says the legacy JSON/XML EnumMarshaller classes were removed in Grails 8, and that projects needing the old verbose enum-object contract must register a custom object marshaller explicitly.

Section 31: extension API compatibility notes

The new section is scoped to applications, plugins, or build logic that directly import public Grails extension classes or override public extension points. It explicitly says unsupported internal implementation details are not covered.

The section is intentionally compact so the upgrade guide remains usable as a migration checklist rather than a full japicmp report.

Review feedback addressed

Copilot noted that the first version of the plugin-filter migration note used org.apache.grails.core.plugins.filters.*, which could be read as a wildcard-import recommendation. That feedback was valid.

The note now points to the org.apache.grails.core.plugins.filters package and lists explicit replacement types instead of using .*.

Verification

Completed:

  • Rendered the changed guide file with Asciidoctor.js:
    • npx --yes asciidoctor -o C:\Users\james\AppData\Local\Temp\opencode\upgrading80x.html grails-doc\src\en\guide\upgrading\upgrading80x.adoc
  • Confirmed the rendered HTML contains the corrected enum-marshaller text and the new compatibility section.
  • Re-rendered after review feedback fixes and confirmed the replacement package text renders without a .* reference.
  • Ran git diff --check successfully.
  • Ran review-gate on the original documentation diff: Oracle GREEN and Codex GREEN for hash a419ca79bbb6441febaabaf2d1b2457ed2831e2f.
  • Ran review-gate on the final follow-up diff: Oracle GREEN and Codex GREEN for staged/pushed hash a2cf68206237041a8f04553ddfd571b49475903f.

Attempted but blocked by an unrelated current-tree issue:

  • ./gradlew.bat :grails-doc:publishGuide -x aggregateGroovydoc
  • The Gradle run did not reach the docs task because :grails-validation:compileGroovy fails in the current tree with unable to resolve class groovy.transform.Virtual in grails-validation/src/main/groovy/grails/validation/Validateable.groovy.

No further Gradle commands were run after being asked to run Gradle tasks one at a time, with a max of two tasks per invocation.

Correct the enum marshaller migration note and add a Grails 8 extension API compatibility section for confirmed binary breaks.

Assisted-by: Hephaestus:openai/gpt-5.5
Copilot AI review requested due to automatic review settings July 4, 2026 16:02

Copilot AI 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.

Pull request overview

Updates the Grails 8 upgrade guide to more accurately describe confirmed Grails 8 binary/API compatibility breaks and to correct guidance around enum marshalling changes.

Changes:

  • Clarifies that the legacy enum marshaller classes were removed in Grails 8 and that there is no config switch to restore the verbose enum-object shape.
  • Adds a new “Extension API Compatibility Notes” section summarizing confirmed binary breaks across multiple Grails modules and providing migration pointers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread grails-doc/src/en/guide/upgrading/upgrading80x.adoc Outdated
@bito-code-review

Copy link
Copy Markdown

The suggestion to avoid wildcard imports is appropriate. You can update the migration note to reference the package explicitly or list the specific classes to improve clarity and follow best practices.

Here is the updated text for that row in the table:

| `grails-core`
| `grails.plugins.PluginFilter`, `IncludingPluginFilter`, `ExcludingPluginFilter`, `NoOpPluginFilter`, and related filter types
| Import the replacements from the `org.apache.grails.core.plugins.filters` package.

grails-doc/src/en/guide/upgrading/upgrading80x.adoc

| `grails-core`
| `grails.plugins.PluginFilter`, `IncludingPluginFilter`, `ExcludingPluginFilter`, `NoOpPluginFilter`, and related filter types
| Import the replacements from the `org.apache.grails.core.plugins.filters` package.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 49.5713%. Comparing base (c88070e) to head (3793e0a).

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #15823        +/-   ##
==================================================
- Coverage     49.5810%   49.5713%   -0.0097%     
+ Complexity      16695      16689         -6     
==================================================
  Files            1947       1947                
  Lines           92598      92598                
  Branches        16179      16179                
==================================================
- Hits            45911      45902         -9     
- Misses          39589      39596         +7     
- Partials         7098       7100         +2     

see 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Clarify plugin filter replacement imports and complete the japicmp checklist coverage for core constructor wiring and fields taglib helpers.

Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
Assisted-by: Hephaestus:openai/gpt-5.5
@testlens-app

testlens-app Bot commented Jul 5, 2026

Copy link
Copy Markdown

🚨 TestLens detected 5 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI - Groovy Joint Validation Build / build_grails > :grails-test-examples-gorm:integrationTest

Test Runs
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP

CI / Build Grails-Core (windows-latest, 25) > :grails-shell-cli:test

Test Runs
RunningApplicationProcessSpec > stop terminates a running process and removes the PID file

CI / Functional Tests (Java 21, indy=false) > :grails-test-examples-gorm:integrationTest

Test Runs
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP

CI / Functional Tests (Java 21, indy=true) > :grails-test-examples-gorm:integrationTest

Test Runs
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP

CI / Functional Tests (Java 25, indy=false) > :grails-test-examples-gorm:integrationTest

Test Runs
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP

🏷️ Commit: 2f58a24
▶️ Tests: 39320 executed
⚪️ Checks: 44/44 completed

Test Failures

RunningApplicationProcessSpec > stop terminates a running process and removes the PID file (:grails-shell-cli:test in CI / Build Grails-Core (windows-latest, 25))
Condition not satisfied:

!process.isAlive()
||       |
||       true
|Process[pid=11440, exitValue=1]
false

	at org.grails.cli.gradle.RunningApplicationProcessSpec.stop terminates a running process and removes the PID file(RunningApplicationProcessSpec.groovy:217)
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP (:grails-test-examples-gorm:integrationTest in CI / Functional Tests (Java 21, indy=false))
groovy.lang.MissingPropertyException: No such property: FORM for class: gorm.DirtyCheckBindingSpec
	at gorm.DirtyCheckBindingSpec.an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP(DirtyCheckBindingSpec.groovy:83)
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP (:grails-test-examples-gorm:integrationTest in CI / Functional Tests (Java 21, indy=true))
groovy.lang.MissingPropertyException: No such property: FORM for class: gorm.DirtyCheckBindingSpec
	at gorm.DirtyCheckBindingSpec.an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP(DirtyCheckBindingSpec.groovy:83)
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP (:grails-test-examples-gorm:integrationTest in CI / Functional Tests (Java 25, indy=false))
groovy.lang.MissingPropertyException: No such property: FORM for class: gorm.DirtyCheckBindingSpec
	at gorm.DirtyCheckBindingSpec.an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP(DirtyCheckBindingSpec.groovy:83)
DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP (:grails-test-examples-gorm:integrationTest in CI - Groovy Joint Validation Build / build_grails)
groovy.lang.MissingPropertyException: No such property: FORM for class: gorm.DirtyCheckBindingSpec
	at gorm.DirtyCheckBindingSpec.an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP(DirtyCheckBindingSpec.groovy:83)

Muted Tests

Select tests to mute in this pull request:

  • DirtyCheckBindingSpec > an explicit bindable:true id constraint declared on a @DirtyCheck base is inherited and binds over HTTP
  • RunningApplicationProcessSpec > stop terminates a running process and removes the PID file

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

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.

2 participants