jamesfredley opened a new issue, #16128:
URL: https://github.com/apache/grails-core/issues/16128

   ### Summary
   
   Any Grails 7 plugin that ships a Groovy `@Immutable` class compiled with 
Groovy 4 fails at runtime on Grails 8 with `NoClassDefFoundError: 
org/apache/groovy/runtime/ObjectUtil`. Groovy 4's `@Immutable` AST 
transformation emits calls to 
`org.apache.groovy.runtime.ObjectUtil.cloneObject(Object)` for defensive 
copying, and that class was removed in Groovy 5.
   
   The plugin resolves, compiles, loads, and boots. The failure only appears 
when the affected class is actually constructed, so a smoke test that merely 
starts the application will pass.
   
   Found while auditing released Grails 7 plugins against `8.0.0-M5`, using 
`io.github.matrei:grails-inertia:3.0.0` (latest release, catalog constraint 
`7.0.0 > *`).
   
   ### Grails Version
   
   8.0.0-M5 (Groovy 5.0.8, Spring Boot 4.1.0, Spring Framework 7.0.8, JDK 
21.0.11)
   
   ### Steps to Reproduce
   
   In a stock `create-app --profile=web` application on 8.0.0-M5:
   
   ```groovy
   implementation "io.github.matrei:grails-inertia:3.0.0"
   ```
   
   Complete the plugin's documented client setup (`npm install && npm run 
build`, which produces the Vite manifest the plugin requires outside 
development), add a controller action that calls the plugin's documented 
`renderInertia(...)`, then issue a real request:
   
   ```text
   GET /inertia-probe    with header    X-Inertia: true
   ```
   
   ### Actual Behaviour
   
   The application starts normally - `Tomcat started on port 54517`, and the 
plugin appears in the load order as `inertia (3.0.0)`. The request returns HTTP 
500:
   
   ```text
   jakarta.servlet.ServletException: Handler dispatch failed:
           java.lang.NoClassDefFoundError: org/apache/groovy/runtime/ObjectUtil
        at 
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:978)
        ...
        at grails.plugin.inertia.InertiaPage.<init>(InertiaPage.groovy)
        at grails.plugin.inertia.Inertia.createJsonModel(Inertia.groovy:148)
        at grails.plugin.inertia.InertiaTrait$Trait$Helper.renderInertia(...)
   ```
   
   ### Root Cause
   
   `org.apache.groovy.runtime.ObjectUtil` exists in Groovy 4 and is gone in 
Groovy 5. Verified directly against the jars on the resolved classpath:
   
   ```text
   groovy-4.0.32.jar : org/apache/groovy/runtime/ObjectUtil.class present = True
   groovy-5.0.8.jar  : org/apache/groovy/runtime/ObjectUtil.class present = 
False
   groovy-3.0.25.jar : org/apache/groovy/runtime/ObjectUtil.class present = 
False
   ```
   
   The plugin's `InertiaPage` is a Groovy immutable class - its constant pool 
carries `groovy.transform.KnownImmutable` and a call to 
`org.codehaus.groovy.transform.ImmutableASTTransformation.checkPropNames`:
   
   ```text
   #13  = Utf8    Lgroovy/transform/KnownImmutable;
   #145 = Utf8    org/codehaus/groovy/transform/ImmutableASTTransformation
    41: invokestatic  #150  // 
ImmutableASTTransformation.checkPropNames:(Ljava/lang/Object;Ljava/util/Map;)V
   ```
   
   and the Groovy 4 `@Immutable` transformation compiled **22** defensive-copy 
call sites into that single class:
   
   ```text
    77: invokestatic  #68   // Method 
org/apache/groovy/runtime/ObjectUtil.cloneObject:(Ljava/lang/Object;)Ljava/lang/Object;
    87: invokestatic  #68   // Method 
org/apache/groovy/runtime/ObjectUtil.cloneObject:(Ljava/lang/Object;)Ljava/lang/Object;
   102: invokestatic  #68   // Method 
org/apache/groovy/runtime/ObjectUtil.cloneObject:(Ljava/lang/Object;)Ljava/lang/Object;
   ... (22 total)
   ```
   
   Every one of those `invokestatic` targets is missing at runtime under Groovy 
5, so constructing the class throws.
   
   ### Expected Behaviour
   
   At minimum this belongs in the Grails 8 upgrade guide's Groovy 5 section: a 
Groovy 4-compiled `@Immutable` class cannot run on Groovy 5 and must be 
recompiled. Because the emitted call sites are generated by the AST 
transformation rather than written by the author, nothing in the plugin's 
source hints at the dependency, and there is no build-time signal.
   
   ### Why this is worth tracking here
   
   The trigger is a plain, idiomatic Groovy annotation, not an exotic API:
   
   - `@Immutable` is ordinary Groovy used widely for value/DTO types.
   - The failure survives dependency resolution, compilation, plugin load, and 
application startup.
   - The error names a Groovy internal class and gives no indication that an 
`@Immutable` class or a stale plugin is responsible.
   - It only surfaces when that class is instantiated, so it can hide behind 
untested code paths.
   
   This is the third distinct Groovy 4 -> 5 binary break found in this sweep, 
after #16123 (generic trait with fields) and #16126 (AST transformation 
targeting a private trait method). Together they suggest the upgrade guide 
would benefit from a consolidated "Groovy 4-compiled plugins can fail at 
runtime" section, since all three pass every build-time check.
   
   ### Distinct from previously reported issues
   
   Not #16122 (Metadata dynamic key access), #16123 (`$Trait$FieldHelper` / 
`MalformedParameterizedTypeException`), #16124 (partial Jackson 2 classpath), 
#16125 (pre-Apache `org.grails` coordinates), #16126 (private trait method lost 
by an AST transformation), or #16127 (Spring 7 `HttpHeaders` no longer 
implementing `MultiValueMap`). This one is a removed Groovy runtime class 
referenced by `@Immutable`-generated code.
   
   ### Notes
   
   Part of a compatibility sweep of released Grails 7 plugins against 8.0.0-M5. 
In the same sweep `cloud.wondrify:i18n-asset-pipeline-grails:5.2.0-M1`, 
`cloud.wondrify:karman-grails:3.0.6`, and 
`io.github.gpc:phone-number-constraint:2.0.0` passed verification against their 
documented public APIs.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to