+1 opt-in 

Walter

> On Jul 21, 2026, at 4:11 PM, James Fredley <[email protected]> wrote:
> 
> Plugin support for newer version is a real sore spot for Grails.  What is the 
> argument for non-default, opt-in support for Grails 7 plugins in Grails 8 
> apps?
> 
> https://github.com/apache/grails-core/pull/15947 ended up as on opt-in for 
> the breaking changes
> 
> James
> 
> On 2026/07/21 20:44:12 Søren Berg Glasius wrote:
>> Hi all,
>> 
>> +1 with James D.
>> 
>> Making legacy support an explicit opt-in (legacyCommandSupport = true) with
>> loud detection makes total sense. It avoids dragging legacy debt into every
>> Grails 8 build by default while providing an easy, well-documented path for
>> those who need it.
>> 
>> Let's move forward with this approach and the proposed testing gates.
>> 
>> Den tirs. 21. jul. 2026 kl. 22.22 skrev James Daugherty <
>> [email protected]>:
>> 
>>> Do we plan to revert every breaking API change then?  Deprecations have
>>> already broken multiple plugins (I recently fixed the grails web console in
>>> one example). Why is changing a single flag to enable compatibility not
>>> acceptable while removing an entire API is? This inconsistently does not
>>> make sense to me.
>>> 
>>> On 2026/07/21 19:52:25 James Fredley wrote:
>>>> I am taking another spin through
>>> https://github.com/apache/grails-core/pull/16011 in an attempt to get
>>> this closer to a compromise.
>>>> 
>>>> But I am strongly of the opinion that existing, already published Grails
>>> 7 plugins should work in Grails 8, by default with warnings.  And I think
>>> we should keep the compatibility shims for an extended period of time.
>>>> 
>>>> If we do not have to break end apps, I would like to keep them working,
>>> while still doing the split and letting teams move to the modern plugin
>>> approach, at their own pace.
>>>> 
>>>> On the 1 of 4, it seems like the other 3 could also have similar
>>> classpath issues, based on what they import.
>>>> 
>>>> James
>>>> 
>>>> On 2026/07/21 16:52:21 James Daugherty wrote:
>>>>> Hi all,
>>>>> 
>>>>> Following James Fredley's -1 on the CLI split, I want to lay out where
>>>>> we actually agree, correct a couple of points in the record, and
>>>>> propose a concrete path so we can settle this on the list rather than
>>>>> in PR or voting threads.
>>>>> 
>>>>> Where we agree
>>>>> 
>>>>> Nobody is proposing that users wait on plugin re-releases. We all
>>>>> agree on the goal: an application upgrading to Grails 8 must be able
>>>>> to keep using commands from already-published, unchanged Grails 7
>>>>> plugins. #16011 provides a mechanism for that, and after the review
>>>>> iterations its core design is right: the deprecated
>>>>> grails.dev.commands.* contract lives in a separate, execution-only
>>>>> grails-core-cli-legacy artifact, off every compile classpath,
>>>>> discovered through a neutral SPI, so grails-core-cli carries zero
>>>>> legacy references and retiring the window later means dropping one
>>>>> artifact. I support that design and want it to land.
>>>>> 
>>>>> Correcting the record
>>>>> 
>>>>> 1. "We only addressed 1 of 4 paths that provide commands." #16011's
>>>>> own audit table reaches the opposite conclusion: of the four
>>>>> mechanisms (application commands, codegen scripts, YAML commands,
>>>>> ServiceLoader/profile commands), only application commands were broken
>>>>> by the split. Codegen scripts, YAML, and ServiceLoader commands were
>>>>> verified unaffected. The hyphenated-script issue is a separate Groovy
>>>>> 5 regression that exists independently of #15948 and is fixed in
>>>>> #16011. So the compatibility gap is one mechanism, not four — and it's
>>>>> the one both PRs are focused on.
>>>>> 
>>>>> 2. "#15948 was merged before a path was identified." It was merged
>>>>> into a milestone, explicitly with the note that the design would
>>>>> iterate before an RC. Milestones exist precisely so breaking changes
>>>>> can land and the compatibility story can be worked out before anything
>>>>> users depend on ships. Nothing has GA'd; no user is currently
>>>>> stranded. Framing a milestone merge as the final word — and a -1 as
>>>>> the response — escalates a design discussion we were already having.
>>>>> 
>>>>> The actual open disagreement: default-on vs. explicit opt-in
>>>>> 
>>>>> I am firmly for shipping the compatibility layer, and firmly against
>>>>> provisioning it by default. My proposal keeps the upgrade
>>>>> zero-confusion without committing the framework to zero-touch support
>>>>> of a deprecated ABI:
>>>>> 
>>>>> - Detection always-on, provisioning opt-in. When the build or registry
>>>>> sees a grails.dev.commands.ApplicationCommand entry on the classpath
>>>>> and grails { legacyCommandSupport = true } is not set, it fails loudly
>>>>> with exact remediation: "Plugin X ships Grails 7 commands; set grails
>>>>> { legacyCommandSupport = true } or upgrade the plugin." Nothing is
>>>>> silent. The discoverability concern behind default-on is fully
>>>>> answered. The user flips one documented switch during an upgrade they
>>>>> are already performing.
>>>>> 
>>>>> - Precedent. This is how we've handled every comparable transition:
>>>>> indy, and the BOM-handling change in this very release, shipped behind
>>>>> explicit settings. The wider ecosystem does the same: Spring Boot did
>>>>> not keep Jackson 2 on the classpath by default next to Jackson 3; you
>>>>> add it explicitly if you need it. I know of no precedent for
>>>>> default-provisioning a deprecated contract onto every build, test
>>>>> runtime included.
>>>>> 
>>>>> - Costs of default-on fall on people who don't need it. Every Grails 8
>>>>> application would carry grails-core-cli-legacy and the
>>>>> dual-classloader factories scan on its command and test-runtime
>>>>> classpaths for the lifetime of the window.
>>>>> 
>>>>> - Convergence. Default-on removes the only pressure that makes the
>>>>> deprecation window converge. A one-time runtime warning doesn't reach
>>>>> plugin authors — they never see their users' logs. And when the
>>>>> artifact is eventually dropped, plugins that never migrated break at
>>>>> that point with no action ever having been asked of anyone — a silent
>>>>> break outside the visible opt-in contract. A visible switch is the
>>>>> signal, to users and authors both, that migration is actually
>>>>> expected.
>>>>> 
>>>>> What must be true before we advertise the compat contract (regardless
>>>>> of the default)
>>>>> 
>>>>> These are in my review of #16011 and I consider them merge/RC gates,
>>>>> not follow-ups:
>>>>> 
>>>>> 1. Prove the headline claim. The current fixture recompiles legacy
>>>>> sources with Groovy 5, which answers a different question than whether
>>>>> a genuine Grails 7/Groovy 4 trait-consumer binary links against the
>>>>> restored traits. The repo's existing composite-build pattern
>>>>> (includeBuild) gives us a pinned grails-core:7.1.1 fixture without any
>>>>> toolchain fight. Until that two-app test is green, "unchanged plugins
>>>>> keep working without a re-release" is a hope, not a contract — and
>>>>> it's the exact claim the -1 rests on.
>>>>> 
>>>>> 2. No nondeterministic task graphs. The legacy per-command task
>>>>> discovery resolves runtimeClasspath eagerly in afterEvaluate under
>>>>> catch (Throwable), making the task set scheduling-dependent. Either
>>>>> drop the legacy per-command tasks (the generic runCommand -Pargs=...
>>>>> already works) or source names from the lenient withDependencies
>>>>> mechanism the companion discovery uses. We spent Grails 7 removing
>>>>> exactly this pattern.
>>>>> 
>>>>> 3. Loud linkage failures. A LinkageError loading a legacy command is
>>>>> the compat layer failing its contract, not a faulty plugin; it must
>>>>> log at error level with clear framework-bug wording, distinct from
>>>>> ordinary per-command isolation.
>>>>> 
>>>>> Proposal
>>>>> 
>>>>> Land #16011 with: the separate-artifact design as-is;
>>>>> detection-always-on + legacyCommandSupport opt-in; the precompiled
>>>>> 7.1.1 fixture via composite build; legacy per-command tasks dropped in
>>>>> favor of runCommand; the LinkageError split. Document the switch in
>>>>> the upgrade notes as a one-line step.
>>>>> 
>>>>> To be clear, I'm not asking James to carry these items alone — I'm
>>>>> offering to do the heavy lifting on them. Specifically, I'll take the
>>>>> precompiled 7.1.1 fixture (the composite-build wiring is the pattern I
>>>>> set up for grails-gradle and build-logic, so it's fastest for me to
>>>>> stand up), and I'm happy to do the opt-in gating and detection wiring
>>>>> in the Gradle plugin as well, either as commits into #16011 or as a
>>>>> stacked PR on top of it, whichever James prefers. The LinkageError
>>>>> split is a few lines either of us can do. None of this needs to slow
>>>>> the PR down.
>>>>> 
>>>>> If the community consensus after discussion is genuinely default-on,
>>>>> I'll accept that outcome — but let's decide it here, deliberately,
>>>>> with the trade-offs on the table. With the gates above shared between
>>>>> us, I think we can have this landed well before the RC either way.
>>>>> 
>>>> 
>>> 
>> 

Reply via email to