diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index fd6e9c648..6faf7f3db 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -108,8 +108,11 @@ 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" + # 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 28004714d..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 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)). 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 @@ -57,17 +58,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 is pinned to GraalVM 25.4.4.1.1. + +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 +88,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/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/infra/sam-graalvm/Dockerfile b/examples/powertools-examples-cloudformation/infra/sam-graalvm/Dockerfile index dac9390e5..b3e9cfa97 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 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-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/Dockerfile b/examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile index 8377d6dc7..ddd5139a9 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 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/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/Dockerfile b/examples/powertools-examples-idempotency/sam-graalvm/Dockerfile index dac9390e5..b3e9cfa97 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 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/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/Dockerfile b/examples/powertools-examples-parameters/sam-graalvm/Dockerfile index 8377d6dc7..ddd5139a9 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 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/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/Dockerfile b/examples/powertools-examples-serialization/sam-graalvm/Dockerfile index 8377d6dc7..ddd5139a9 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 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/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..56a6af29c 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 @@ -502,6 +502,14 @@ --native-image-info -H:+UnlockExperimentalVMOptions -H:+ReportExceptionStackTraces + + -H:+ClassForNameRespectsClassLoader @@ -690,45 +698,24 @@ - - - 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 + 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 diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 017f798c2..5014e3855 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -217,7 +217,7 @@ org.graalvm.buildtools native-maven-plugin - 0.11.5 + 1.1.14 true