From 9918f531e3aa4a8eabcc2338b1f86c134692aa3d Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Sat, 29 Aug 2026 19:32:11 -0700 Subject: [PATCH 1/2] Compile CodecMetaClassSupport statically The static initializer added by #15800 builds its Caffeine cache through three dynamic call sites: ldc class com/github/benmanes/caffeine/cache/Caffeine invokedynamic invoke:(Ljava/lang/Class;)Ljava/lang/Object; // newBuilder() invokedynamic invoke:(Ljava/lang/Object;)Ljava/lang/Object; // weakKeys() invokedynamic invoke:(Ljava/lang/Object;)Ljava/lang/Object; // build() In a GraalVM native image there is no method handle to link, so Groovy falls back to IndyInterface.aotDispatch and resolves through the metaclass, which is built from Caffeine.getDeclaredMethods() -- empty unless the class is registered for reflection. The class initializer fails with No signature of static method: newBuilder for class: com.github.benmanes.caffeine.cache.Caffeine and codecLookup, gspTagLibraryLookup and groovyPagesTemplateEngine fail with it, so the application never starts. Six of the ten methods already carried @CompileStatic. Moving it to the class turns those three call sites into invokestatic/invokevirtual and needs no reachability metadata at all. addMetaMethod stays dynamic: it resolves a metamethod by GString property name, which is the one thing static compilation cannot express. --- .../org/grails/encoder/CodecMetaClassSupport.groovy | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/grails-encoder/src/main/groovy/org/grails/encoder/CodecMetaClassSupport.groovy b/grails-encoder/src/main/groovy/org/grails/encoder/CodecMetaClassSupport.groovy index a5c79e02eee..d5e70a8ba71 100644 --- a/grails-encoder/src/main/groovy/org/grails/encoder/CodecMetaClassSupport.groovy +++ b/grails-encoder/src/main/groovy/org/grails/encoder/CodecMetaClassSupport.groovy @@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode import org.codehaus.groovy.runtime.GStringImpl @@ -39,6 +40,7 @@ import grails.util.GrailsMetaClassUtils * @author Lari Hotari * @since 2.3 */ +@CompileStatic class CodecMetaClassSupport { static final Object[] EMPTY_ARGS = [] @@ -53,7 +55,6 @@ class CodecMetaClassSupport { * * @param codecClass the codec class */ - @CompileStatic void configureCodecMethods(CodecFactory codecFactory, boolean cacheLookup = !Environment.getCurrent().isDevelopmentMode(), List targetMetaClasses = resolveDefaultMetaClasses()) { Closure encodeMethodNameClosure = { String codecName -> "${ENCODE_AS_PREFIX}${codecName}".toString() } Closure decodeMethodNameClosure = { String codecName -> "${DECODE_PREFIX}${codecName}".toString() } @@ -133,12 +134,10 @@ class CodecMetaClassSupport { * @param delegate * @return */ - @CompileStatic private static Object filterNullObject(Object delegate) { delegate != null && delegate.getClass() != NullObject ? delegate : null } - @CompileStatic private addAliasMetaMethods(List targetMetaClasses, Set aliases, Closure methodNameClosure, Closure methodClosure, boolean cacheLookup, Set registeredMetaMethodKeys) { aliases?.each { String aliasName -> @@ -162,6 +161,9 @@ class CodecMetaClassSupport { } } + // The metamethod name is only known at run time, and a GString property name is the one + // thing static compilation cannot express. + @CompileDynamic protected void addMetaMethod(List targetMetaClasses, String methodName, Closure closure) { targetMetaClasses.each { ExpandoMetaClass emc -> emc."${methodName}" << closure @@ -185,18 +187,15 @@ class CodecMetaClassSupport { } } - @CompileStatic private static boolean shouldRegisterMetaMethod(ExpandoMetaClass emc, String methodName, Set registeredMetaMethodKeys) { MetaMethodRegistrationKey key = registrationKey(emc, methodName) registeredMetaMethodKeys.add(key) || emc.getMetaMethod(methodName, EMPTY_ARGS) == null } - @CompileStatic private static MetaMethodRegistrationKey registrationKey(ExpandoMetaClass emc, String methodName) { new MetaMethodRegistrationKey(emc.getTheClass(), methodName) } - @CompileStatic private static Set registeredMetaMethodKeys(CodecFactory codecFactory) { REGISTERED_META_METHODS.get(codecFactory) { CodecFactory ignored -> Collections.newSetFromMap(new ConcurrentHashMap()) From 6703374a16499e28cb16e79aad440a89b327bfb0 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Sat, 29 Aug 2026 20:21:39 -0700 Subject: [PATCH 2/2] Bump Forge Shadow plugin to 8.3.11 for Gradle 9 Shadow 8.3.6's ShadowApplicationPlugin configures the startShadowScripts task through conventionMapping.map('mainClassName'). Gradle 9 removed the deprecated CreateStartScripts.mainClassName in favour of the mainClass Property, so the task can no longer be created and the Forge build fails at configuration time: Could not determine the dependencies of task ':grails-forge-web-netty:shadowDistTar'. > Could not create task ':grails-forge-web-netty:startShadowScripts'. > You can't map a property that does not exist: propertyName=mainClassName Shadow is only applied to grails-forge-web-netty because the AWS Elastic Beanstalk packaging came over from 7.2.x, which builds Forge on Gradle 8.14.5 where mainClassName still exists. Shadow 8.3.7 backported Gradle 9 support and 8.3.11 is the latest 8.3.x; the 8.x line is maintenance-only upstream, so Forge's own build should follow the generated applications to Shadow 9 (already 9.2.2 in the Forge dependency catalog) in a later change, which needs the Transformer -> ResourceTransformer migration in buildSrc. --- grails-forge/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-forge/gradle.properties b/grails-forge/gradle.properties index 936cc5e2ad4..f9666505fbd 100644 --- a/grails-forge/gradle.properties +++ b/grails-forge/gradle.properties @@ -46,7 +46,7 @@ postgresSocketFactoryVersion=1.17.1 picocliVersion=4.7.6 reflectionsVersion=0.10.2 rockerVersion=2.2.1 -shadowVersion=8.3.6 +shadowVersion=8.3.11 spotlessVersion=6.25.0 testcontainersVersion=1.20.4 testRetryVersion=1.6.2