Skip to content

feat(v2): Add GraalVM reachability metadata for core utilities #1753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fd40160
Initial version of PT common GraalVM reachability metadata
rr-on-gh Nov 29, 2024
ee03ae4
Initial version of PT logging GraalVM reachability metadata
rr-on-gh Nov 29, 2024
866fd82
Initial version of PT serialization GraalVM reachability metadata
rr-on-gh Nov 29, 2024
b72280f
Initial version of PT logging log4j GraalVM reachability metadata
rr-on-gh Nov 29, 2024
97650c0
Initial version of PT logging log4j GraalVM reachability metadata
rr-on-gh Nov 29, 2024
276984d
Initial version of PT logging logback GraalVM reachability metadata
rr-on-gh Nov 29, 2024
63105bf
Example of PT logging with GraalVM
rr-on-gh Dec 2, 2024
1a2cad2
Added GRM for powertools-metrics and update the example
rr-on-gh Dec 3, 2024
90fe66e
Added GRM for powertools-metrics and update the example
rr-on-gh Dec 3, 2024
617a57c
Cleanup of junit and mockito references
rr-on-gh Dec 3, 2024
a0791a4
Cleanup of junit and mockito references
rr-on-gh Dec 3, 2024
7bfedb3
Added documentation
rr-on-gh Dec 5, 2024
e867523
Added GraalVM documentation
rr-on-gh Mar 7, 2025
9b0b623
Added GraalVM documentation
rr-on-gh Mar 7, 2025
9759aad
Merge branch 'v2' into graalvm-logging-v2
phipag Mar 10, 2025
13f5cc4
Added GRM files for tracing module. Also changed the tests to use @Se…
rr-on-gh Mar 10, 2025
c45325a
Merge remote-tracking branch 'origin/graalvm-logging-v2' into graalvm…
rr-on-gh Mar 10, 2025
a969e3c
Switch commons module to use JUnit Pioneer
rr-on-gh Mar 11, 2025
ea9a1a2
Switch logging module to use JUnit Pioneer
rr-on-gh Mar 11, 2025
d96539b
Switch logging-log4j module to use JUnit Pioneer
rr-on-gh Mar 11, 2025
d186e91
Switch metrics module to use JUnit Pioneer
rr-on-gh Mar 11, 2025
e879e06
Fix testGetVersionFromProperties_InvalidFile test when running as nat…
phipag Mar 12, 2025
a1651ae
Cleaned up documentation and fixed review comments
rr-on-gh Mar 12, 2025
9b2b6e8
Cosmetic fixes/formatting. Re-order imports in changed test files, fi…
phipag Mar 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions GraalVM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# GraalVM Compatibility for AWS Lambda Powertools Java

## Table of Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [General Implementation Steps](#general-implementation-steps)
- [Known Issues and Solutions](#known-issues-and-solutions)
- [Reference Implementation](#reference-implementation)

## Overview
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
- Maven 3.x

## General Implementation Steps
GraalVM native image compilation requires complete knowledge of an application's dynamic features at build time. The GraalVM reachability metadata (GRM) JSON files are essential because Java applications often use features that are determined at runtime, such as reflection, dynamic proxy classes, resource loading, and JNI (Java Native Interface). The metadata files tell GraalVM which classes need reflection access, which resources need to be included in the native image, and which proxy classes need to be generated.

In order to generate the metadata reachability files for Powertools for Lambda, follow these general steps.

1. **Add Maven Profiles**
- Add profile for generating GraalVM reachability metadata files. You can find an example of this in profile `generate-graalvm-files` of this [pom.xml](powertools-common/pom.xml).
- Add another profile for running the tests in the native image. You can find and example of this in profile `graalvm-native` of this [pom.xml](powertools-common/pom.xml).

2. **Generate Reachability Metadata**
- Set the `JAVA_HOME` environment variable to use GraalVM
- Run tests with `-Pgenerate-graalvm-files` profile.
```shell
mvn -Pgenerate-graalvm-files clean test
```

3. **Validate Native Image Tests**
- Set the `JAVA_HOME` environment variable to use GraalVM
- Run tests with `-Pgraalvm-native` profile. This will build a GraalVM native image and run the JUnit tests.
```shell
mvn -Pgraalvm-native clean test
```

4. **Clean Up Metadata**
- GraalVM metadata reachability files generated in Step 2 contains references to the test scoped dependencies as well.
- Remove the references in generated metadata files for the following (and any other references to test scoped resources and classes):
- JUnit
- Mockito
- ByteBuddy

## Known Issues and Solutions
1. **Mockito Compatibility**
- Powertools uses Mockito 5.x which uses “inline mock maker” as the default. This mock maker does not play well with GraalVM. Mockito [recommends](https://github.com/mockito/mockito/releases/tag/v5.0.0) using subclass mock maker with GraalVM. Therefore `generate-graalvm-files` profile uses subclass mock maker instead of inline mock maker.
- 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. **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

## Reference Implementation
Working example is available in the [examples](examples/powertools-examples-core-utilities/sam-graalvm).
111 changes: 110 additions & 1 deletion docs/FAQs.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,113 @@ The following example shows how to use the Lambda Powertools Parameters module w
}
```
The `aws-crt-client` was considered for adoption as the default HTTP client in Lambda Powertools for Java as mentioned in [Move SDK http client to CRT](https://github.com/aws-powertools/powertools-lambda-java/issues/1092),
but due to the impact on the developer experience it was decided to stick with the `url-connection-client`.
but due to the impact on the developer experience it was decided to stick with the `url-connection-client`.

## How can I use Powertools for AWS Lambda (Java) with GraalVM?

Powertools core utilities, i.e. [logging](./core/logging.md), [metrics](./core/metrics.md) and [tracing](./core/tracing.md), include the [GraalVM Reachability Metadata (GRM)](https://www.graalvm.org/latest/reference-manual/native-image/metadata/) in the `META-INF` directories of the respective JARs. You can find a working example of Serverless Application Model (SAM) based application in the [examples](../examples/powertools-examples-core-utilities/sam-graalvm/README.md) directory.

Below, you find typical steps you need to follow in a Maven based Java project:

### Set the environment to use GraalVM

```shell
export JAVA_HOME=<path to GraalVM>
```

### Use log4j `>2.24.0`
Log4j version `2.24.0` adds [support for GraalVM](https://github.com/apache/logging-log4j2/issues/1539#issuecomment-2106766878). Depending on your project's dependency hierarchy, older version of log4j might be included in the final dependency graph. Make sure version `>2.24.0` of these dependencies are used by your Maven project:

```xml
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>

```

### Add the AWS Lambda Java Runtime Interface Client dependency

The Runtime Interface Client allows your function to receive invocation events from Lambda, send the response back to Lambda, and report errors to the Lambda service. Add the below dependency to your Maven project:

```xml
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
<version>2.1.1</version>
</dependency>
```

Also include the AWS Lambda GRM files by copying the `com.amazonaws` [directory](../examples/powertools-examples-core-utilities/sam-graalvm/src/main/resources/META-INF/native-image/) in your project's `META-INF/native-image` directory

### Build the native image

Use the `native-maven-plugin` to build the native image. You can do this by adding the plugin to your `pom.xml` and creating a build profile called `native-image` that can build the native image of your Lambda function:

```xml
<profiles>
<profile>
<id>native-image</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.1</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<imageName>your-project-name</imageName>
<mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass>
<buildArgs>
<!-- required for AWS Lambda Runtime Interface Client -->
<arg>--enable-url-protocols=http</arg>
<arg>--add-opens java.base/java.util=ALL-UNNAMED</arg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
```

Create a Docker image using a `Dockerfile` like [this](../examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile) to create an x86 based build image.

```shell
docker build --platform linux/amd64 . -t your-org/your-app-graalvm-builder
```

Create the native image of you Lambda function using the Docker command below.

```shell
docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 your-org/your-app-graalvm-builder mvn clean -Pnative-image package

```
The native image is created in the `target/` directory.
6 changes: 3 additions & 3 deletions docs/core/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ we provide [built-in JMESPath expressions](#built-in-correlation-id-expressions)

#### Custom keys

** Using StructuredArguments **
**Using StructuredArguments**

To append additional keys in your logs, you can use the `StructuredArguments` class:

Expand Down Expand Up @@ -624,7 +624,7 @@ To append additional keys in your logs, you can use the `StructuredArguments` cl
}
```

** Using MDC **
**Using MDC**

Mapped Diagnostic Context (MDC) is essentially a Key-Value store. It is supported by the [SLF4J API](https://www.slf4j.org/manual.html#mdc){target="_blank"},
[logback](https://logback.qos.ch/manual/mdc.html){target="_blank"} and log4j (known as [ThreadContext](https://logging.apache.org/log4j/2.x/manual/thread-context.html){target="_blank"}). You can use the following standard:
Expand Down Expand Up @@ -1048,4 +1048,4 @@ Use the `LambdaEcsEncoder` rather than the `LambdaJsonEncoder` when configuring
<appender-ref ref="console" />
</root>
</configuration>
```
```
14 changes: 14 additions & 0 deletions examples/powertools-examples-core-utilities/sam-graalvm/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#Use the official AWS SAM base image for Java 21
FROM public.ecr.aws/sam/build-java21:latest

#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

#Make native image and mvn available on CLI
RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image
RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn

#Set GraalVM as default
ENV JAVA_HOME=/usr/lib/graalvm
ENV PATH=/usr/lib/graalvm/bin:$PATH
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build-HelloWorldFunction:
mvn clean package -P native-image
chmod +x target/hello-world
cp target/hello-world $(ARTIFACTS_DIR) # (ARTIFACTS_DIR --> https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/custom_make/DESIGN.md#implementation)
chmod +x src/main/config/bootstrap
cp src/main/config/bootstrap $(ARTIFACTS_DIR)
64 changes: 64 additions & 0 deletions examples/powertools-examples-core-utilities/sam-graalvm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Powertools for AWS Lambda (Java) - Core Utilities Example with SAM on GraalVM

This project demonstrates the Lambda for Powertools Java module deployed using [Serverless Application Model](https://aws.amazon.com/serverless/sam/) running as a GraalVM native image.

For general information on the deployed example itself, you can refer to the parent [README](../README.md)

## Configuration

- SAM uses [template.yaml](template.yaml) to define the application's AWS resources.
This file defines the Lambda function to be deployed as well as API Gateway for it.

- Set the environment to use GraalVM

```shell
export JAVA_HOME=<path to GraalVM>
```

## Build the sample application

- Build the Docker image that will be used as the environment for SAM build:

```shell
docker build --platform linux/amd64 . -t powertools-examples-core-sam-graalvm
```

- Build the SAM project using the docker image

```shell
sam build --use-container --build-image powertools-examples-core-sam-graalvm
```

#### [Optional] Building with -SNAPSHOT versions of PowerTools

- If you are testing the example with a -SNAPSHOT version of PowerTools, the maven build inside the docker image will fail. This is because the -SNAPSHOT version of the PowerTools library that you are working on is still not available in maven central/snapshot repository.
To get around this, follow these steps:
- Create the native image using the `docker` command below on your development machine. The native image is created in the `target` directory.
- `` docker run --platform linux/amd64 -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 powertools-examples-core-sam-graalvm mvn clean -Pnative-image package -DskipTests ``
- Edit the [`Makefile`](Makefile) remove this line
- `mvn clean package -P native-image`
- Build the SAM project using the docker image
- `sam build --use-container --build-image powertools-examples-core-sam-graalvm`

## Deploy the sample application

- SAM deploy

```shell
sam deploy
```

To deploy the example, check out the instructions for getting
started with SAM in [the examples directory](../../README.md)

## Additional notes

You can watch the trace information or log information using the SAM CLI:

```bash
# Tail the logs
sam logs --tail $MY_STACK

# Tail the traces
sam traces --tail
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"body": "{\"message\": \"hello world\"}",
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": false,
"queryStringParameters": {
"foo": "bar"
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "/prod/path/to/resource",
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}

Loading