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]