codeconsole opened a new pull request, #16274:
URL: https://github.com/apache/grails-core/pull/16274

   ## Description
   
   `CodecMetaClassSupport` cannot initialize inside a GraalVM native image. Its 
static initializer, added by #15800, builds the Caffeine cache through three 
dynamic call sites:
   
   ```
    7: ldc           class com/github/benmanes/caffeine/cache/Caffeine
   10: invokedynamic invoke:(Ljava/lang/Class;)Ljava/lang/Object;   // 
newBuilder()
   15: invokedynamic invoke:(Ljava/lang/Object;)Ljava/lang/Object;  // 
weakKeys()
   20: invokedynamic invoke:(Ljava/lang/Object;)Ljava/lang/Object;  // build()
   ```
   
   In an image there is no method handle to link, so Groovy falls back to 
`IndyInterface.aotDispatch`, which resolves through the metaclass. The 
metaclass is built from `Caffeine.getDeclaredMethods()`, and that is empty 
unless `com.github.benmanes.caffeine.cache.Caffeine` is registered for 
reflection. So `<clinit>` throws:
   
   ```
   Caused by: java.lang.ExceptionInInitializerError
        at 
org.grails.commons.DefaultGrailsCodecClass.configureCodecMethods(DefaultGrailsCodecClass.java:329)
        at 
org.grails.plugins.codecs.DefaultCodecLookup.registerCodec(DefaultCodecLookup.java:63)
        ...
   Caused by: groovy.lang.MissingMethodException: No signature of static 
method: newBuilder
        for class: com.github.benmanes.caffeine.cache.Caffeine
        at 
org.grails.encoder.CodecMetaClassSupport.<clinit>(CodecMetaClassSupport.groovy:47)
   ```
   
   `codecLookup` fails, then `Sitemesh3LayoutTagLib`, then 
`gspTagLibraryLookup`, then `groovyPagesTemplateEngine`, and the application 
never starts.
   
   Registering `Caffeine` for reflection works around it, but the native-image 
agent will not record the need: on a JVM that call site links a `MethodHandle` 
directly and never consults the metaclass, so a traced 
`reachability-metadata.json` comes back without it. The gap only shows up in 
the image.
   
   Six of the ten methods already carried `@CompileStatic`. Moving it to the 
class removes the problem at the source — the three call sites become 
`invokestatic`/`invokevirtual` and need no metadata:
   
   ```
    7: invokestatic  
Caffeine.newBuilder:()Lcom/github/benmanes/caffeine/cache/Caffeine;
   10: invokevirtual 
Caffeine.weakKeys:()Lcom/github/benmanes/caffeine/cache/Caffeine;
   13: invokevirtual Caffeine.build:()Lcom/github/benmanes/caffeine/cache/Cache;
   ```
   
   `addMetaMethod` keeps dynamic dispatch under `@CompileDynamic`: it resolves 
a metamethod by GString property name (`emc."${methodName}" << closure`), which 
is the one thing static compilation cannot express.
   
   Net effect is −6/+5 lines: one class-level annotation replacing six 
method-level ones, plus `@CompileDynamic` on the single method that needs it. 
No behavior change on the JVM.
   
   Related: #16176 (verify native-image behaviour with an end-to-end native 
build).
   
   ## Contributor Checklist
   
   ### Issue and Scope
   
   - [x] This PR is linked to an existing issue that has been **acknowledged or 
approved** by the project team. — No dedicated issue; background above. The 
defect is a hard startup failure in a native image, introduced by #15800 when 
it merged on 2026-08-10.
   - [x] This PR addresses the **complete scope** of the linked issue.
   - [x] This PR contains a **single, focused change**.
   - [x] This PR targets the **correct branch** for the type of change.
   
   ### Code Quality
   
   - [ ] I have **added or updated tests** that cover the changes introduced in 
this PR. — No new test. This is a compilation-strategy change with no 
JVM-observable behavior change; the existing `CodecMetaClassSupportSpec` covers 
both the cached path and the `addMetaMethod` extension hook, which is the 
method that stays dynamic. The defect it fixes is only observable in a native 
image, which #16176 tracks.
   - [ ] I have verified that all existing tests pass by running `./gradlew 
build --rerun-tasks`. — Ran `:grails-encoder:test` (4 passed, 0 failed) and 
`:grails-encoder:compileGroovy`. Full build not run locally; leaving that to CI.
   - [x] My code follows the project's **code style** guidelines. — `./gradlew 
:grails-encoder:codeStyle` passes.
   - [x] This PR does **not** include mass reformatting, style-only changes, or 
large-scale refactoring.
   
   ### Licensing and Attribution
   
   - [x] All contributed code is provided under the Apache License 2.0, and new 
source files include the appropriate **Apache license header**. — No new files.
   - [x] I have the necessary rights to submit this contribution.
   
   ### Documentation
   
   - [x] If this PR introduces user-facing changes, I have included or updated 
the relevant documentation. — Internal change, no user-facing API.
   - [x] The PR description clearly explains **what** was changed and **why**.
   


-- 
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