From a84389638151df49af2c5e28359c55e14505cef3 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 14:21:47 +0200 Subject: [PATCH 1/9] chore(ci): run GraalVM native tests on GraalVM 25 innovation release Move the graalvm-build job from GraalVM 21.0.8 to the latest GraalVM 25 innovation release (setup-graalvm version "25i-latest"). GraalVM 25.0.x LTS bundles ASM 9.7.1, which reads class files up to major version 68. On JDK 25, Byte Buddy generates Mockito classes with major version 69, and ClassPredefinitionFeature fails with "Unsupported class file major version 69" (oracle/graal#12723). Innovation releases parse class files with the Java Class-File API. Also move the sam-graalvm example build images to sam/build-java25 and GraalVM 25, and document the known issue in GraalVM.md. Refs #2416 --- .github/workflows/check-build.yml | 6 ++++-- GraalVM.md | 16 ++++++++++------ .../infra/sam-graalvm/Dockerfile | 8 ++++---- .../sam-graalvm/Dockerfile | 8 ++++---- .../sam-graalvm/Dockerfile | 8 ++++---- .../sam-graalvm/Dockerfile | 8 ++++---- .../sam-graalvm/Dockerfile | 8 ++++---- 7 files changed, 34 insertions(+), 28 deletions(-) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index fd6e9c648..eeef40c86 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -108,8 +108,10 @@ jobs: - name: Setup GraalVM uses: graalvm/setup-graalvm@27b360dbb857524eaf0b4c4cef364d38e5b4f173 # v1.6.7 with: - # Pinned to 21.0.8 due to unsafeAllocated enforcement in 21.0.10+. See #2416 - java-version: "21.0.8" + java-version: "25" + # GraalVM 25.0 LTS cannot read Java 25 class files for predefined classes (oracle/graal#12723). + # Innovation releases include the fix. See #2416 + version: "25i-latest" distribution: "graalvm" cache: maven - id: graalvm-native-test diff --git a/GraalVM.md b/GraalVM.md index 28004714d..2f6fc44b3 100644 --- a/GraalVM.md +++ b/GraalVM.md @@ -11,7 +11,7 @@ This documentation provides guidance for adding GraalVM support for AWS Lambda Powertools Java modules and using the modules in Lambda functions. ## Prerequisites -- GraalVM 21+ installation +- GraalVM 25 installation. To run the native tests of modules that use Mockito, use a GraalVM 25 innovation release (see [Known Issues and Solutions](#known-issues-and-solutions)) - Maven 3.x ## General Implementation Steps @@ -57,17 +57,21 @@ mvn -Pnative test - Subclass mock maker does not support testing static methods. Tests have therefore been modified to use [JUnit Pioneer](https://junit-pioneer.org/docs/environment-variables/) to inject the environment variables in the scope of the test's execution. 2. **Unsafe Allocation Tracing** - - GraalVM 21.0.10+ requires `"unsafeAllocated": true` in `reflect-config.json` for classes instantiated via `Unsafe.allocateInstance()`. Mockito uses Objenesis which relies on this. + - GraalVM 21.0.10 and later, including GraalVM 25, require `"unsafeAllocated": true` in the reachability metadata for classes instantiated via `Unsafe.allocateInstance()`. Mockito uses Objenesis which relies on this. - The `enableExperimentalUnsafeAllocationTracing` option is enabled in the root `pluginManagement` agent configuration to address this. -3. **Log4j Compatibility** +3. **Predefined Classes on GraalVM 25.0** + - **Issue**: On GraalVM 25.0.x, native test image builds fail with `Unsupported class file major version 69` in `ClassPredefinitionFeature`. The tracing agent records the classes that Byte Buddy generates for Mockito mocks at runtime (`enableExperimentalPredefinedClasses`). On JDK 25, these classes use class file version 69, which the ASM version shaded in GraalVM 25.0 cannot read ([oracle/graal#12723](https://github.com/oracle/graal/issues/12723)). + - **Solution**: Use a GraalVM 25 innovation release (25.1 or later), which reads class files with the Java Class-File API. The CI `graalvm-build` job uses the latest GraalVM 25 innovation release. + +4. **Log4j Compatibility** - Version 2.22.1 fails with this error ``` java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported. ``` - This has been [fixed](https://github.com/apache/logging-log4j2/discussions/2364#discussioncomment-8950077) in Log4j 2.24.x. PT has been updated to use this version of Log4j -4. **Test Class Organization** +5. **Test Class Organization** - **Issue**: Anonymous inner classes and lambda expressions in Mockito matchers cause `NoSuchMethodError` in GraalVM native tests - **Solution**: - Extract static inner test classes to separate concrete classes in the same package as the class under test @@ -83,12 +87,12 @@ java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defini }) ``` -5. **Package Visibility Issues** +6. **Package Visibility Issues** - **Issue**: Test handler classes cannot access package-private methods when placed in subpackages - **Solution**: Place test handler classes in the same package as the class under test, not in subpackages like `handlers/` - **Example**: Use `software.amazon.lambda.powertools.cloudformation` instead of `software.amazon.lambda.powertools.cloudformation.handlers` -6. **Test Stubs Best Practice** +7. **Test Stubs Best Practice** - **Best Practice**: Avoid mocking where possible and use concrete test stubs provided by `powertools-common` package - **Solution**: Use `TestLambdaContext` and other test stubs from `powertools-common` test-jar instead of Mockito mocks - **Implementation**: Add `powertools-common` test-jar dependency and replace `mock(Context.class)` with `new TestLambdaContext()` diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile index dac9390e5..0a2fa4e8a 100644 --- a/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile @@ -1,9 +1,9 @@ -# Use the official AWS SAM base image for Java 21 -FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 +# Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df # Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz -RUN mv graalvm-jdk-21.* /usr/lib/graalvm +RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm # Make native image and mvn available on CLI RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile index 8377d6dc7..c42ed9305 100644 --- a/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile @@ -1,9 +1,9 @@ -#Use the official AWS SAM base image for Java 21 -FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 +#Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz -RUN mv graalvm-jdk-21.* /usr/lib/graalvm +RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image diff --git a/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile index dac9390e5..0a2fa4e8a 100644 --- a/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile @@ -1,9 +1,9 @@ -# Use the official AWS SAM base image for Java 21 -FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 +# Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df # Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz -RUN mv graalvm-jdk-21.* /usr/lib/graalvm +RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm # Make native image and mvn available on CLI RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image diff --git a/examples/powertools-examples-parameters/sam-graalvm/Dockerfile b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile index 8377d6dc7..c42ed9305 100644 --- a/examples/powertools-examples-parameters/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile @@ -1,9 +1,9 @@ -#Use the official AWS SAM base image for Java 21 -FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 +#Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz -RUN mv graalvm-jdk-21.* /usr/lib/graalvm +RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image diff --git a/examples/powertools-examples-serialization/sam-graalvm/Dockerfile b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile index 8377d6dc7..c42ed9305 100644 --- a/examples/powertools-examples-serialization/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile @@ -1,9 +1,9 @@ -#Use the official AWS SAM base image for Java 21 -FROM public.ecr.aws/sam/build-java21@sha256:a5554d68374e19450c6c88448516ac95a9acedc779f318040f5c230134b4e461 +#Use the official AWS SAM base image for Java 25 +FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz | tar -xvz -RUN mv graalvm-jdk-21.* /usr/lib/graalvm +RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image From 2eee8b2e8e28c15d41dc0309906dd61f9316fc33 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 14:21:49 +0200 Subject: [PATCH 2/9] build(deps): upgrade native-maven-plugin to 1.1.14 Upgrade native-maven-plugin from 0.11.5 to 1.1.14 in the root pom, the sam-graalvm examples and the e2e test handlers. Plugin 1.x uses the 1.0 reachability metadata repository in the unified reachability-metadata.json format, which requires GraalVM 25. Remove the temporary native-junit6-jdk21 profile. Plugin 1.1.0 and later initialize the JUnit 6 discovery classes at build time (graalvm/native-build-tools#794). Supersedes the Dependabot bump to native-maven-plugin 1.1.14. Refs #2416 --- docs/FAQs.md | 2 +- .../pom.xml | 2 +- .../sam-graalvm/pom.xml | 2 +- .../sam-graalvm/pom.xml | 2 +- .../sam-graalvm/pom.xml | 2 +- .../sam-graalvm/pom.xml | 2 +- pom.xml | 32 +------------------ powertools-e2e-tests/handlers/pom.xml | 2 +- 8 files changed, 8 insertions(+), 38 deletions(-) diff --git a/docs/FAQs.md b/docs/FAQs.md index cea4b774f..338620d61 100644 --- a/docs/FAQs.md +++ b/docs/FAQs.md @@ -218,7 +218,7 @@ Use the `native-maven-plugin` to build the native image. You can do this by addi org.graalvm.buildtools native-maven-plugin - 0.10.1 + 1.1.14 true diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml index 3d7055331..4037bcbcd 100644 --- a/examples/powertools-examples-cloudformation/pom.xml +++ b/examples/powertools-examples-cloudformation/pom.xml @@ -159,7 +159,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml b/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml index 42234b2d6..7ba8f1a41 100644 --- a/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml +++ b/examples/powertools-examples-core-utilities/sam-graalvm/pom.xml @@ -158,7 +158,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true diff --git a/examples/powertools-examples-idempotency/sam-graalvm/pom.xml b/examples/powertools-examples-idempotency/sam-graalvm/pom.xml index 2c2ac5e3b..1ba04a41a 100644 --- a/examples/powertools-examples-idempotency/sam-graalvm/pom.xml +++ b/examples/powertools-examples-idempotency/sam-graalvm/pom.xml @@ -139,7 +139,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true diff --git a/examples/powertools-examples-parameters/sam-graalvm/pom.xml b/examples/powertools-examples-parameters/sam-graalvm/pom.xml index 7ad9a80bf..6db830c09 100644 --- a/examples/powertools-examples-parameters/sam-graalvm/pom.xml +++ b/examples/powertools-examples-parameters/sam-graalvm/pom.xml @@ -175,7 +175,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true diff --git a/examples/powertools-examples-serialization/sam-graalvm/pom.xml b/examples/powertools-examples-serialization/sam-graalvm/pom.xml index f6264754a..8a0521126 100644 --- a/examples/powertools-examples-serialization/sam-graalvm/pom.xml +++ b/examples/powertools-examples-serialization/sam-graalvm/pom.xml @@ -61,7 +61,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true diff --git a/pom.xml b/pom.xml index 5b3d31fd2..35186dd9f 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ 5.23.0 2.3.0 1.5.0 - 0.11.5 + 1.1.14 @@ -690,36 +690,6 @@ - - - native-junit6-jdk21 - - (,22) - - - - - - org.graalvm.buildtools - native-maven-plugin - - - --initialize-at-build-time=org.junit.jupiter.engine.discovery.MethodSegmentResolver,org.junit.platform.launcher.core.DiscoveryIssueReportingDiscoveryListener - - - - - - - native + + true + From 5d07d498e439befd8b84518a53c9c21368535192 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 14:49:18 +0200 Subject: [PATCH 4/9] fix(ci): make Class.forName respect class loaders in native tests Mockito creates mocks with Byte Buddy's ByteArrayClassLoader, which loads each generated class with Class.forName(name, false, this). Up to GraalVM 25.0, Class.forName fell back to ClassLoader#loadClass when the image contained predefined classes. GraalVM 25.1 and later route Class.forName through ClassRegistries, which ignores the class loader unless ClassForNameRespectsClassLoader is enabled. Byte Buddy never defines the predefined mock class, and all Mockito tests fail with "Cannot load class ...$MockitoMock$..." caused by ClassNotFoundException. Enable -H:+ClassForNameRespectsClassLoader for native test images. GraalVM 25.1 added the equivalent --future-defaults=class-for-name-respects-class-loader (GR-71698), but GraalVM 25.0 rejects that value. The experimental option is available in both. --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 537d26c72..26693c397 100644 --- a/pom.xml +++ b/pom.xml @@ -502,6 +502,14 @@ --native-image-info -H:+UnlockExperimentalVMOptions -H:+ReportExceptionStackTraces + + -H:+ClassForNameRespectsClassLoader From c6a6a05dee15aa1d487e68503bbb30aff32a3ce7 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 15:21:12 +0200 Subject: [PATCH 5/9] fix(common): continue class preloading when a class fails to initialize ClassPreLoader only caught ClassNotFoundException. A LinkageError from Class.forName, e.g. an ExceptionInInitializerError or an UnsatisfiedLinkError, stopped the preloading and propagated out of the CRaC beforeCheckpoint hooks. With -H:+ClassForNameRespectsClassLoader on GraalVM 25.4, the tracing native test TracingUtilsTest.testBeforeCheckpointDoesNotThrowException fails this way. Class.forName now initializes com.sun.management.internal.PlatformMBeanProviderImpl from the tracing classesloaded.txt, and its static initializer cannot load the management_ext native library in the native image. Catch LinkageError, log it at debug level and continue with the next class. --- .../common/internal/ClassPreLoader.java | 5 +++++ .../common/internal/ClassPreLoaderTest.java | 15 +++++++++++++++ .../src/test/resources/classesloaded.txt | 1 + 3 files changed, 21 insertions(+) diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java index 77318722c..faadc94a6 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/ClassPreLoader.java @@ -96,6 +96,11 @@ private static boolean loadClassIfFound(String className) { } catch (ClassNotFoundException e) { // No action is required if the class with given name cannot be found return false; + } catch (LinkageError e) { + // A class can fail to link or initialize in the current runtime, e.g. a JDK class that needs a native + // library that is not available in a GraalVM native image. Skip it and continue with the next class. + LOG.debug("SnapStart priming: failed to preload class {}", className, e); + return false; } } } \ No newline at end of file diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java index fb1359434..d6998e19b 100644 --- a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/ClassPreLoaderTest.java @@ -34,6 +34,19 @@ static class LeadingSpaceDummyClass { } } + /** + * Dummy class whose static initializer fails. The class name is referenced before {@link DummyClass} in + * powertools-common/src/test/resources/classesloaded.txt. + * This class is used to verify that the ClassPreLoader continues after a class fails to initialize. + */ + static class FailingInitializerDummyClass { + static final Object VALUE = fail(); + + private static Object fail() { + throw new IllegalStateException("Failing static initializer"); + } + } + @Test void preloadClasses_shouldIgnoreInvalidClassesAndLoadValidClasses() { @@ -43,6 +56,8 @@ void preloadClasses_shouldIgnoreInvalidClassesAndLoadValidClasses() { // preloadClasses once and asserts on all classes listed in classesloaded.txt together. // powertools-common/src/test/resources/classesloaded.txt has a class that does not exist. // Verify that the missing class did not throw any exception + // It also has a class whose static initializer fails. Verify that the ExceptionInInitializerError + // did not stop the preloading of the following classes assertDoesNotThrow(ClassPreLoader::preloadClasses); // When the classloaded.txt is a mixed bag of valid and invalid classes, valid classes must load diff --git a/powertools-common/src/test/resources/classesloaded.txt b/powertools-common/src/test/resources/classesloaded.txt index a6cda309e..04dee49e5 100644 --- a/powertools-common/src/test/resources/classesloaded.txt +++ b/powertools-common/src/test/resources/classesloaded.txt @@ -1,3 +1,4 @@ software.amazon.lambda.powertools.common.internal.NonExistingClass +software.amazon.lambda.powertools.common.internal.ClassPreLoaderTest$FailingInitializerDummyClass software.amazon.lambda.powertools.common.internal.ClassPreLoaderTest$DummyClass software.amazon.lambda.powertools.common.internal.ClassPreLoaderTest$LeadingSpaceDummyClass From 104605b00ec50ee90f0981d6545bcbb57573bdf5 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 16:03:23 +0200 Subject: [PATCH 6/9] fix(examples): remove stray curl argument in sam-graalvm Dockerfiles The GraalVM download step ran "curl -4 -L curl ", so curl also tried to fetch a host named "curl". Use the same command as the e2e test Dockerfile. --- .../infra/sam-graalvm/Dockerfile | 2 +- .../powertools-examples-core-utilities/sam-graalvm/Dockerfile | 2 +- examples/powertools-examples-idempotency/sam-graalvm/Dockerfile | 2 +- examples/powertools-examples-parameters/sam-graalvm/Dockerfile | 2 +- .../powertools-examples-serialization/sam-graalvm/Dockerfile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile index 0a2fa4e8a..b3e9cfa97 100644 --- a/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df # Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz RUN mv graalvm-jdk-25.* /usr/lib/graalvm # Make native image and mvn available on CLI diff --git a/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile index c42ed9305..ddd5139a9 100644 --- a/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI diff --git a/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile index 0a2fa4e8a..b3e9cfa97 100644 --- a/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df # Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz RUN mv graalvm-jdk-25.* /usr/lib/graalvm # Make native image and mvn available on CLI diff --git a/examples/powertools-examples-parameters/sam-graalvm/Dockerfile b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile index c42ed9305..ddd5139a9 100644 --- a/examples/powertools-examples-parameters/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI diff --git a/examples/powertools-examples-serialization/sam-graalvm/Dockerfile b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile index c42ed9305..ddd5139a9 100644 --- a/examples/powertools-examples-serialization/sam-graalvm/Dockerfile +++ b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/sam/build-java25@sha256:35c398d9eaadd173262573be05ba7e1c3eda552a5bd320659b7b1442632b47df #Install GraalVM dependencies -RUN curl -4 -L curl https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz +RUN curl -4 -L https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz | tar -xvz RUN mv graalvm-jdk-25.* /usr/lib/graalvm #Make native image and mvn available on CLI From 2ee642bdbec9da613f38314e215eb7957f4bd656 Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 16:03:25 +0200 Subject: [PATCH 7/9] chore(ci): pin GraalVM native tests to GraalVM 25.4.4.1.1 Replace the floating "25i-latest" with the exact innovation release, so the graalvm-build job is reproducible, as with the earlier 21.0.8 pin. setup-graalvm v1.6.7 matches the version input against the GDS artifact version metadata, which is "25.4.4.1.1" for this release. Document the CI GraalVM version in GraalVM.md. Also state that the sam-graalvm example Dockerfiles use the GraalVM for JDK 25 LTS release: they only build native images of the examples and do not run the tracing-agent native tests, so oracle/graal#12723 does not affect them. Refs #2416 --- .github/workflows/check-build.yml | 7 ++++--- GraalVM.md | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index eeef40c86..6faf7f3db 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -109,9 +109,10 @@ jobs: uses: graalvm/setup-graalvm@27b360dbb857524eaf0b4c4cef364d38e5b4f173 # v1.6.7 with: java-version: "25" - # GraalVM 25.0 LTS cannot read Java 25 class files for predefined classes (oracle/graal#12723). - # Innovation releases include the fix. See #2416 - version: "25i-latest" + # Pinned to the GraalVM 25.4.4.1.1 innovation release. GraalVM 25.0 LTS cannot read Java 25 class files + # for predefined classes (oracle/graal#12723). Move back to the LTS release once the fix is backported. + # See #2416 + version: "25.4.4.1.1" distribution: "graalvm" cache: maven - id: graalvm-native-test diff --git a/GraalVM.md b/GraalVM.md index 2f6fc44b3..551619774 100644 --- a/GraalVM.md +++ b/GraalVM.md @@ -11,7 +11,8 @@ This documentation provides guidance for adding GraalVM support for AWS Lambda Powertools Java modules and using the modules in Lambda functions. ## Prerequisites -- GraalVM 25 installation. To run the native tests of modules that use Mockito, use a GraalVM 25 innovation release (see [Known Issues and Solutions](#known-issues-and-solutions)) +- GraalVM 25 installation. To run the native tests of modules that use Mockito, use a GraalVM 25 innovation release (see [Known Issues and Solutions](#known-issues-and-solutions)). The CI `graalvm-build` job in [check-build.yml](.github/workflows/check-build.yml) runs the native tests on Oracle GraalVM 25.4.4.1.1. +- The `sam-graalvm` example Dockerfiles use the GraalVM for JDK 25 LTS release from `https://download.oracle.com/graalvm/25/latest/`. They only build native images of the example functions and do not run the native tests with the tracing agent, so the known issue does not affect them. - Maven 3.x ## General Implementation Steps @@ -62,7 +63,7 @@ mvn -Pnative test 3. **Predefined Classes on GraalVM 25.0** - **Issue**: On GraalVM 25.0.x, native test image builds fail with `Unsupported class file major version 69` in `ClassPredefinitionFeature`. The tracing agent records the classes that Byte Buddy generates for Mockito mocks at runtime (`enableExperimentalPredefinedClasses`). On JDK 25, these classes use class file version 69, which the ASM version shaded in GraalVM 25.0 cannot read ([oracle/graal#12723](https://github.com/oracle/graal/issues/12723)). - - **Solution**: Use a GraalVM 25 innovation release (25.1 or later), which reads class files with the Java Class-File API. The CI `graalvm-build` job uses the latest GraalVM 25 innovation release. + - **Solution**: Use a GraalVM 25 innovation release (25.1 or later), which reads class files with the Java Class-File API. The CI `graalvm-build` job is pinned to GraalVM 25.4.4.1.1. 4. **Log4j Compatibility** - Version 2.22.1 fails with this error From 906dfabdf2b19564a7e799e502aae17d80796d9a Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 16:40:09 +0200 Subject: [PATCH 8/9] docs(ci): describe native-maven-plugin 1.x argLine handling in the native profile The comment said that NativeExtension overwrites the surefire argLine. That was the 0.11.x behavior. 1.x appends the tracing agent to the existing argLine (verified with help:effective-pom -Pnative -Dagent=true), so the jdk16 add-opens flags are kept. Keep JDK_JAVA_OPTIONS as is and explain why JaCoCo is skipped. --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 26693c397..624739af3 100644 --- a/pom.xml +++ b/pom.xml @@ -700,15 +700,15 @@ native From 9d86b4626ffb4772c94186c74bb88c33be43daec Mon Sep 17 00:00:00 2001 From: Philipp Page Date: Sat, 10 Oct 2026 17:15:57 +0200 Subject: [PATCH 9/9] docs(ci): state that JDK_JAVA_OPTIONS in the native profile is a safeguard native-maven-plugin 1.x keeps the jdk16 add-opens flags in the surefire argLine (NativeExtension.appendAgentArgument). JDK_JAVA_OPTIONS is no longer required, but it stays as a safeguard. Comment-only change. --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 624739af3..56a6af29c 100644 --- a/pom.xml +++ b/pom.xml @@ -702,8 +702,9 @@ The native profile configures surefire to pass add-opens flags via JDK_JAVA_OPTIONS. When -Dagent=true is set, the native-maven-plugin's NativeExtension appends the tracing agent to the existing surefire argLine, so the add-opens flags from the jdk16 profile are kept. native-maven-plugin - 0.11.x replaced the argLine instead and dropped them. JDK_JAVA_OPTIONS is picked up by the JVM - automatically, so the tests get the add-opens flags regardless of how the argLine is built. + 0.11.x replaced the argLine instead and dropped them. JDK_JAVA_OPTIONS is no longer required with 1.x, + but it is kept as a safeguard: the JVM picks it up automatically, so the tests get the add-opens flags + regardless of how the argLine is built. The native profile also skips JaCoCo. Because the tracing agent is appended to the existing argLine, the JaCoCo agent would instrument classes that Byte Buddy defines at runtime before the tracing agent