diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml
index 267d901c..c8064513 100644
--- a/.github/workflows/aws-lambda-java-core.yml
+++ b/.github/workflows/aws-lambda-java-core.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
index 66f6b2bf..285848a9 100644
--- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
+++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml
index 04ab53a5..b3b360b4 100644
--- a/.github/workflows/aws-lambda-java-events.yml
+++ b/.github/workflows/aws-lambda-java-events.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml
index 7ae54cbe..03718e60 100644
--- a/.github/workflows/aws-lambda-java-log4j2.yml
+++ b/.github/workflows/aws-lambda-java-log4j2.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml
new file mode 100644
index 00000000..88032095
--- /dev/null
+++ b/.github/workflows/aws-lambda-java-profiler.yml
@@ -0,0 +1,74 @@
+name: Run integration tests for aws-lambda-java-profiler
+
+on:
+ pull_request:
+ branches: [ '*' ]
+ paths:
+ - 'experimental/aws-lambda-java-profiler/**'
+ - '.github/workflows/aws-lambda-java-profiler.yml'
+ push:
+ branches: ['*']
+ paths:
+ - 'experimental/aws-lambda-java-profiler/**'
+ - '.github/workflows/aws-lambda-java-profiler.yml'
+
+jobs:
+
+ build:
+ runs-on: ubuntu-latest
+
+ permissions:
+ id-token: write
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: corretto
+
+ - name: Issue AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }}
+ role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }}
+ role-session-name: GitHubActionsRunIntegrationTests
+ role-duration-seconds: 900
+
+ - name: Build layer
+ working-directory: ./experimental/aws-lambda-java-profiler/extension
+ run: ./build_layer.sh
+
+ - name: Publish layer
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/publish_layer.sh
+
+ - name: Create the bucket layer
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/create_bucket.sh
+
+ - name: Create Java function
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/create_function.sh
+
+ - name: Invoke Java function
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/invoke_function.sh
+
+ - name: Download from s3
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/download_from_s3.sh
+
+ - name: Upload profiles
+ uses: actions/upload-artifact@v4
+ with:
+ name: profiles
+ path: /tmp/s3-artifacts
+
+ - name: cleanup
+ if: always()
+ working-directory: ./experimental/aws-lambda-java-profiler
+ run: ./integration_tests/cleanup.sh
\ No newline at end of file
diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml
index c24c48d7..b2700e08 100644
--- a/.github/workflows/aws-lambda-java-serialization.yml
+++ b/.github/workflows/aws-lambda-java-serialization.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml
index a28bca88..1b818014 100644
--- a/.github/workflows/aws-lambda-java-tests.yml
+++ b/.github/workflows/aws-lambda-java-tests.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml
index 25f05029..300341c1 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -16,7 +16,7 @@ jobs:
env:
IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
if: ${{ env.IS_CONFIGURED == 'true' }}
- uses: repo-sync/github-sync@v2
name: Sync repo to branch
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index 7db9d1aa..8909d56b 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -28,7 +28,7 @@ jobs:
contents: read
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
@@ -47,6 +47,10 @@ jobs:
- name: Available buildx platforms
run: echo ${{ steps.buildx.outputs.platforms }}
+ - name: Build and install serialization dependency locally
+ working-directory: ./aws-lambda-java-serialization
+ run: mvn clean install -DskipTests
+
- name: Test Runtime Interface Client xplatform build - Run 'build' target
working-directory: ./aws-lambda-java-runtime-interface-client
run: make build
@@ -86,6 +90,6 @@ jobs:
- name: Upload coverage to Codecov
if: env.CODECOV_TOKEN != null
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index 35a3334b..35c6ca06 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -15,13 +15,21 @@ jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
java-version: 8
distribution: corretto
+
+ - name: Build and install core dependency locally
+ working-directory: ./aws-lambda-java-core
+ run: mvn clean install
+
+ - name: Build and install serialization dependency locally
+ working-directory: ./aws-lambda-java-serialization
+ run: mvn clean install -DskipTests
- name: Runtime Interface Client smoke tests - Run 'pr' target
working-directory: ./aws-lambda-java-runtime-interface-client
@@ -32,7 +40,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
@@ -50,6 +58,14 @@ jobs:
- name: Available buildx platforms
run: echo ${{ steps.buildx.outputs.platforms }}
+
+ - name: Build and install core dependency locally
+ working-directory: ./aws-lambda-java-core
+ run: mvn clean install
+
+ - name: Build and install serialization dependency locally
+ working-directory: ./aws-lambda-java-serialization
+ run: mvn clean install -DskipTests
- name: Test Runtime Interface Client xplatform build - Run 'build' target
working-directory: ./aws-lambda-java-runtime-interface-client
@@ -65,6 +81,6 @@ jobs:
- name: Upload coverage to Codecov
if: env.CODECOV_TOKEN != null
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index 960055ad..2b5e7833 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -15,10 +15,10 @@ on:
- '.github/workflows/samples.yml'
jobs:
- build-kinesis-sample:
+ build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
@@ -28,6 +28,9 @@ jobs:
# Install events module
- name: Install events with Maven
run: mvn -B install --file aws-lambda-java-events/pom.xml
+ # Install serialization module
+ - name: Install serialization with Maven
+ run: mvn -B install --file aws-lambda-java-serialization/pom.xml
# Install tests module
- name: Install tests with Maven
run: mvn -B install --file aws-lambda-java-tests/pom.xml
@@ -39,11 +42,25 @@ jobs:
custom-serialization:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@v5
+ # Set up both Java 8 and 21
+ - name: Set up Java 8 and 21
+ uses: actions/setup-java@v4
with:
- java-version: 21
+ java-version: |
+ 8
+ 21
distribution: corretto
+
+ # Install events module using Java 8
+ - name: Install events with Maven
+ run: |
+ export JAVA_HOME=$JAVA_HOME_8_X64
+ mvn -B clean install \
+ -Dmaven.compiler.source=1.8 \
+ -Dmaven.compiler.target=1.8 \
+ --file aws-lambda-java-events/pom.xml
+
# Build custom-serialization samples
- name: install sam
uses: aws-actions/setup-sam@v2
@@ -57,4 +74,3 @@ jobs:
run: cd samples/custom-serialization/moshi && sam build && sam local invoke -e events/event.json | grep 200
- name: test request-stream-handler
run: cd samples/custom-serialization/request-stream-handler && sam build && sam local invoke -e events/event.json | grep 200
-
diff --git a/.gitignore b/.gitignore
index 371bed6b..9f99cc41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,10 @@ dependency-reduced-pom.xml
# snapshot process
aws-lambda-java-runtime-interface-client/pom.xml.versionsBackup
+
+# profiler
+experimental/aws-lambda-java-profiler/integration_tests/helloworld/build
+experimental/aws-lambda-java-profiler/extension/build/
+experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin
+!experimental/aws-lambda-java-profiler/extension/gradle/wrapper/*.jar
+/scratch/
diff --git a/README.md b/README.md
index 4f22b2cc..b5153a87 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ public class HandlerStream implements RequestStreamHandler {
com.amazonaws
aws-lambda-java-core
- 1.2.3
+ 1.3.0
```
@@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler {
com.amazonaws
aws-lambda-java-events
- 3.14.0
+ 3.16.0
```
@@ -139,6 +139,18 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio
```
+## Lambda Profiler Extension for Java - aws-lambda-java-profiler
+
+
+
+
+
+This project allows you to profile your Java functions invoke by invoke, with high fidelity, and no code changes. It
+uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce profiling data and
+automatically uploads the data as flame graphs to S3.
+
+Follow our [Quick Start](experimental/aws-lambda-java-profiler#quick-start) to profile your functions.
+
## Java implementation of the Runtime Interface Client API - aws-lambda-java-runtime-interface-client
[](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-runtime-interface-client)
@@ -151,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.6.0
+ 2.7.0
```
diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md
index ebd0566f..aebc8ecd 100644
--- a/aws-lambda-java-core/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md
@@ -1,3 +1,11 @@
+### September 3, 2025
+`1.4.0`
+- Getter support for x-ray trace ID through the Context object
+
+### May 26, 2025
+`1.3.0`
+- Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545))
+
### August 17, 2023
`1.2.3`:
- Extended logger interface with level-aware logging backend functions
diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml
index 0dd848a9..cca9d0cd 100644
--- a/aws-lambda-java-core/pom.xml
+++ b/aws-lambda-java-core/pom.xml
@@ -5,7 +5,7 @@
com.amazonaws
aws-lambda-java-core
- 1.2.3
+ 1.4.0
jar
AWS Lambda Java Core Library
@@ -36,13 +36,6 @@
1.8
-
-
- sonatype-nexus-staging
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
-
dev
@@ -115,14 +108,12 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.3
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
true
- sonatype-nexus-staging
- https://aws.oss.sonatype.org/
- false
+ central
diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java
index a0850e78..ed9311a1 100644
--- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java
+++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java
@@ -100,4 +100,23 @@ public interface Context {
*/
LambdaLogger getLogger();
+ /**
+ *
+ * Returns the tenant ID associated with the request.
+ *
+ * @return null by default
+ */
+ default String getTenantId() {
+ return null;
+ }
+
+ /**
+ *
+ * Returns the X-Ray trace ID associated with the request.
+ *
+ * @return null by default
+ */
+ default String getXrayTraceId() {
+ return null;
+ }
}
diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml
index 6a2b1735..9b6afece 100644
--- a/aws-lambda-java-events-sdk-transformer/pom.xml
+++ b/aws-lambda-java-events-sdk-transformer/pom.xml
@@ -5,7 +5,7 @@
com.amazonaws
aws-lambda-java-events-sdk-transformer
- 3.1.0
+ 3.1.1
jar
AWS Lambda Java Events SDK Transformer Library
@@ -63,7 +63,7 @@
com.amazonaws
aws-lambda-java-events
- 3.11.2
+ 3.16.1
provided
diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md
index db423e07..43c25d76 100644
--- a/aws-lambda-java-events/README.md
+++ b/aws-lambda-java-events/README.md
@@ -31,6 +31,7 @@
* `CognitoUserPoolPreAuthenticationEvent`
* `CognitoUserPoolPreSignUpEvent`
* `CognitoUserPoolPreTokenGenerationEvent`
+* `CognitoUserPoolPreTokenGenerationEventV2`
* `CognitoUserPoolVerifyAuthChallengeResponseEvent`
* `ConfigEvent`
* `ConnectEvent`
@@ -73,7 +74,7 @@
com.amazonaws
aws-lambda-java-events
- 3.14.0
+ 3.16.0
...
diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md
index 7ddf2bff..a4bcd10a 100644
--- a/aws-lambda-java-events/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md
@@ -1,3 +1,13 @@
+### June 17, 2025
+`3.16.0`:
+- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548))
+
+### January 31, 2025
+`3.15.0`:
+- Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519))
+- Add RotationToken to SecretsManagerRotationEvent ([#520](https://github.com/aws/aws-lambda-java-libs/pull/520))
+
+
### September 13, 2024
`3.14.0`:
- Fix name of s3Bucket field of Task class in S3BatchEventV2 ([#506](https://github.com/aws/aws-lambda-java-libs/pull/506))
diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml
index ec3806fb..925273e9 100644
--- a/aws-lambda-java-events/pom.xml
+++ b/aws-lambda-java-events/pom.xml
@@ -5,7 +5,7 @@
com.amazonaws
aws-lambda-java-events
- 3.14.0
+ 3.16.1
jar
AWS Lambda Java Events Library
diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java
index c7250570..9faeb970 100644
--- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java
+++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java
@@ -127,8 +127,8 @@ public static class AccessTokenGeneration {
@Builder(setterPrefix = "with")
@NoArgsConstructor
public static class GroupOverrideDetails {
- private Map groupsToOverride;
- private Map iamRolesToOverride;
+ private String[] groupsToOverride;
+ private String[] iamRolesToOverride;
private String preferredRole;
}
}
\ No newline at end of file
diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java
index 38547ac2..e9487561 100644
--- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java
+++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java
@@ -59,7 +59,7 @@ public static class ContactData implements Serializable, Cloneable {
private String initiationMethod;
private String instanceArn;
private String previousContactId;
- private String queue;
+ private Queue queue;
private SystemEndpoint systemEndpoint;
}
@@ -80,4 +80,13 @@ public static class SystemEndpoint implements Serializable, Cloneable {
private String address;
private String type;
}
+ @Data
+ @Builder(setterPrefix = "with")
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Queue implements Serializable, Cloneable {
+ private String name;
+ private String ARN;
+ }
+
}
diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java
index dd051d48..aa6c00de 100644
--- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java
+++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java
@@ -43,6 +43,8 @@ public static class KafkaEventRecord {
private String key;
private String value;
private List
diff --git a/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh
new file mode 100755
index 00000000..b69c967a
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+SERIALIZATION_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-serialization"
+
+if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar >/dev/null 2>&1; then
+ echo "RIC jar not found. Please build the project first with 'mvn package'."
+ exit 1
+fi
+
+IMAGE_TAG="java-ric-rie-test"
+
+HANDLER="${1:-EchoHandler::handleRequest}"
+
+echo "Starting RIE test setup for Java..."
+
+# Build local dependencies if not present
+CORE_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-core"
+if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar >/dev/null 2>&1; then
+ echo "Building local aws-lambda-java-core..."
+ (cd "$CORE_ROOT" && mvn package -DskipTests)
+ cp "$CORE_ROOT"/target/aws-lambda-java-core-*.jar "$PROJECT_ROOT/target/"
+fi
+
+if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar >/dev/null 2>&1; then
+ echo "Building local aws-lambda-java-serialization..."
+ (cd "$SERIALIZATION_ROOT" && mvn package -DskipTests)
+ cp "$SERIALIZATION_ROOT"/target/aws-lambda-java-serialization-*.jar "$PROJECT_ROOT/target/"
+fi
+
+echo "Compiling EchoHandler..."
+javac -source 21 -target 21 -cp "$(ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar)" \
+ -d "$PROJECT_ROOT/test-handlers/" "$PROJECT_ROOT/test-handlers/EchoHandler.java"
+
+echo "Building test Docker image..."
+docker build -t "$IMAGE_TAG" -f "$PROJECT_ROOT/Dockerfile.rie" "$PROJECT_ROOT"
+
+echo "Starting test container on port 9000..."
+echo ""
+echo "In another terminal, invoke with:"
+echo "curl -s -X POST -H 'Content-Type: application/json' \"http://localhost:9000/2015-03-31/functions/function/invocations\" -d '{\"message\":\"test\"}'"
+echo ""
+
+exec docker run -it -p 9000:8080 -e _HANDLER="$HANDLER" "$IMAGE_TAG"
\ No newline at end of file
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java
index 986f8b7b..2eeb14e3 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java
@@ -2,6 +2,7 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
+
package com.amazonaws.services.lambda.runtime.api.client;
import com.amazonaws.services.lambda.crac.Core;
@@ -35,7 +36,6 @@
import java.security.Security;
import java.util.Properties;
-
/**
* The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks:
*
@@ -137,6 +137,42 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin
return requestHandler;
}
+ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException {
+ UnsafeUtil.disableIllegalAccessWarning();
+
+ System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8"));
+ System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8"));
+ setupRuntimeLogger(lambdaLogger);
+
+ runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API);
+
+ String taskRoot = System.getProperty("user.dir");
+ String libRoot = "/opt/java";
+ // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes
+ // are loaded from the system classloader.
+ customerClassLoader = new CustomerClassLoader(taskRoot, libRoot, ClassLoader.getSystemClassLoader());
+ Thread.currentThread().setContextClassLoader(customerClassLoader);
+
+ // Load the user's handler
+ LambdaRequestHandler requestHandler = null;
+ try {
+ requestHandler = findRequestHandler(handler, customerClassLoader);
+ } catch (UserFault userFault) {
+ lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ LambdaError error = new LambdaError(
+ LambdaErrorConverter.fromUserFault(userFault),
+ RapidErrorType.BadFunctionCode);
+ runtimeClient.reportInitError(error);
+ System.exit(1);
+ }
+
+ if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) {
+ onInitComplete(lambdaLogger);
+ }
+
+ return requestHandler;
+ }
+
public static void setupRuntimeLogger(LambdaLogger lambdaLogger)
throws ClassNotFoundException {
ReflectUtil.setStaticField(
@@ -176,55 +212,27 @@ private static LogSink createLogSink() {
}
}
- public static void main(String[] args) {
- startRuntime(args[0]);
- }
+ public static void main(String[] args) throws Throwable {
+ try (LambdaContextLogger logger = initLogger()) {
+ LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger);
+ startRuntimeLoop(lambdaRequestHandler, logger);
- private static void startRuntime(String handler) {
- try (LogSink logSink = createLogSink()) {
- LambdaContextLogger logger = new LambdaContextLogger(
- logSink,
- LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL),
- LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT)
- );
- startRuntime(handler, logger);
- } catch (Throwable t) {
+ } catch (IOException | ClassNotFoundException t) {
throw new Error(t);
}
}
- private static void startRuntime(String handler, LambdaContextLogger lambdaLogger) throws Throwable {
- UnsafeUtil.disableIllegalAccessWarning();
-
- System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8"));
- System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8"));
- setupRuntimeLogger(lambdaLogger);
+ private static LambdaContextLogger initLogger() {
+ LogSink logSink = createLogSink();
+ LambdaContextLogger logger = new LambdaContextLogger(
+ logSink,
+ LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL),
+ LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT));
- runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API);
-
- String taskRoot = System.getProperty("user.dir");
- String libRoot = "/opt/java";
- // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes
- // are loaded from the system classloader.
- customerClassLoader = new CustomerClassLoader(taskRoot, libRoot, ClassLoader.getSystemClassLoader());
- Thread.currentThread().setContextClassLoader(customerClassLoader);
+ return logger;
+ }
- // Load the user's handler
- LambdaRequestHandler requestHandler;
- try {
- requestHandler = findRequestHandler(handler, customerClassLoader);
- } catch (UserFault userFault) {
- lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
- LambdaError error = new LambdaError(
- LambdaErrorConverter.fromUserFault(userFault),
- RapidErrorType.BadFunctionCode);
- runtimeClient.reportInitError(error);
- System.exit(1);
- return;
- }
- if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) {
- onInitComplete(lambdaLogger);
- }
+ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable {
boolean shouldExit = false;
while (!shouldExit) {
UserFault userFault = null;
@@ -240,7 +248,7 @@ private static void startRuntime(String handler, LambdaContextLogger lambdaLogge
payload = requestHandler.call(request);
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
// clear interrupted flag in case if it was set by user's code
- boolean ignored = Thread.interrupted();
+ Thread.interrupted();
} catch (UserFault f) {
shouldExit = f.fatal;
userFault = f;
@@ -278,6 +286,7 @@ static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOExce
RapidErrorType.BeforeCheckpointError));
System.exit(64);
}
+
try {
Core.getGlobalContext().afterRestore(null);
} catch (Exception restoreExc) {
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java
index 096bb862..2876499e 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java
@@ -581,6 +581,8 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep
cognitoIdentity,
LambdaEnvironment.FUNCTION_VERSION,
request.getInvokedFunctionArn(),
+ request.getTenantId(),
+ request.getXrayTraceId(),
clientContext
);
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java
index 2ce3b844..20b77262 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java
@@ -22,6 +22,8 @@ public class LambdaContext implements Context {
private final long deadlineTimeInMs;
private final CognitoIdentity cognitoIdentity;
private final ClientContext clientContext;
+ private final String tenantId;
+ private final String xrayTraceId;
private final LambdaLogger logger;
public LambdaContext(
@@ -34,6 +36,8 @@ public LambdaContext(
CognitoIdentity identity,
String functionVersion,
String invokedFunctionArn,
+ String tenantId,
+ String xrayTraceId,
ClientContext clientContext
) {
this.memoryLimit = memoryLimit;
@@ -46,6 +50,8 @@ public LambdaContext(
this.clientContext = clientContext;
this.functionVersion = functionVersion;
this.invokedFunctionArn = invokedFunctionArn;
+ this.tenantId = tenantId;
+ this.xrayTraceId = xrayTraceId;
this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger();
}
@@ -91,6 +97,14 @@ public int getRemainingTimeInMillis() {
return delta > 0 ? delta : 0;
}
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ public String getXrayTraceId() {
+ return xrayTraceId;
+ }
+
public LambdaLogger getLogger() {
return logger;
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java
index b98721eb..f463e7ee 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java
@@ -41,6 +41,7 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel)
if (lambdaContext != null) {
msg.AWSRequestId = lambdaContext.getAwsRequestId();
+ msg.tenantId = lambdaContext.getTenantId();
}
return msg;
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java
index 693eb015..dd356912 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java
@@ -7,9 +7,11 @@
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
import com.amazonaws.services.lambda.runtime.logging.LogLevel;
+import java.io.Closeable;
+import java.io.IOException;
import static java.nio.charset.StandardCharsets.UTF_8;
-public class LambdaContextLogger extends AbstractLambdaLogger {
+public class LambdaContextLogger extends AbstractLambdaLogger implements Closeable {
// If a null string is passed in, replace it with "null",
// replicating the behavior of System.out.println(null);
private static final byte[] NULL_BYTES_VALUE = "null".getBytes(UTF_8);
@@ -29,4 +31,10 @@ protected void logMessage(byte[] message, LogLevel logLevel) {
sink.log(logLevel, this.logFormat, message);
}
}
+
+ @Override
+ public void close() throws IOException {
+ sink.close();
+
+ }
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java
index 5299bffa..0ae19961 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java
@@ -12,4 +12,5 @@ class StructuredLogMessage {
public String message;
public LogLevel level;
public String AWSRequestId;
+ public String tenantId;
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java
index 7bdc2500..656945b4 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java
@@ -40,6 +40,11 @@ public class InvocationRequest {
*/
private String cognitoIdentity;
+ /**
+ * The tenant ID associated with the request.
+ */
+ private String tenantId;
+
private byte[] content;
public String getId() {
@@ -94,6 +99,14 @@ public void setCognitoIdentity(String cognitoIdentity) {
this.cognitoIdentity = cognitoIdentity;
}
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(String tenantId) {
+ this.tenantId = tenantId;
+ }
+
public byte[] getContent() {
return content;
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp
index 7fe47aa4..f0679661 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp
+++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp
@@ -20,6 +20,7 @@ static jfieldID contentField;
static jfieldID clientContextField;
static jfieldID cognitoIdentityField;
static jfieldID xrayTraceIdField;
+static jfieldID tenantIdField;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
@@ -41,6 +42,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
xrayTraceIdField = env->GetFieldID(invocationRequestClass , "xrayTraceId", "Ljava/lang/String;");
clientContextField = env->GetFieldID(invocationRequestClass , "clientContext", "Ljava/lang/String;");
cognitoIdentityField = env->GetFieldID(invocationRequestClass , "cognitoIdentity", "Ljava/lang/String;");
+ tenantIdField = env->GetFieldID(invocationRequestClass, "tenantId", "Ljava/lang/String;");
return JNI_VERSION;
}
@@ -106,6 +108,10 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, cognitoIdentityField, env->NewStringUTF(response.cognito_identity.c_str())));
}
+ if(response.tenant_id != ""){
+ CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, tenantIdField, env->NewStringUTF(response.tenant_id.c_str())));
+ }
+
bytes = reinterpret_cast(response.payload.c_str());
CHECK_EXCEPTION(env, jArray = env->NewByteArray(response.payload.length()));
CHECK_EXCEPTION(env, env->SetByteArrayRegion(jArray, 0, response.payload.length(), bytes));
diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h
index 94e1e22c..d7db5f18 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h
+++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h
@@ -61,6 +61,11 @@ struct invocation_request {
*/
std::chrono::time_point deadline;
+ /**
+ * Tenant ID of the current invocation.
+ */
+ std::string tenant_id;
+
/**
* The number of milliseconds left before lambda terminates the current execution.
*/
diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp
index 91750840..eeaf0e7b 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp
+++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp
@@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context";
static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity";
static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms";
static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn";
+static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id";
enum Endpoints {
INIT,
@@ -301,6 +302,10 @@ runtime::next_outcome runtime::get_next()
req.payload.c_str(),
static_cast(req.get_time_remaining().count()));
}
+
+ if (resp.has_header(TENANT_ID_HEADER)) {
+ req.tenant_id = resp.get_header(TENANT_ID_HEADER);
+ }
return next_outcome(req);
}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java
new file mode 100644
index 00000000..38147d21
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java
@@ -0,0 +1,153 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ClasspathLoaderTest {
+
+ @Test
+ void testLoadAllClassesWithNoClasspath() throws IOException {
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.clearProperty("java.class.path");
+ ClasspathLoader.main(new String[]{});
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithEmptyClasspath() {
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.setProperty("java.class.path", "");
+ assertThrows(FileNotFoundException.class, () ->
+ ClasspathLoader.main(new String[]{}));
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithInvalidPath() {
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.setProperty("java.class.path", "nonexistent/path");
+ assertThrows(FileNotFoundException.class, () ->
+ ClasspathLoader.main(new String[]{}));
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithValidJar(@TempDir Path tempDir) throws IOException {
+ File jarFile = createSimpleJar(tempDir, "test.jar", "TestClass");
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.setProperty("java.class.path", jarFile.getAbsolutePath());
+ ClasspathLoader.main(new String[]{});
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithDirectory(@TempDir Path tempDir) throws IOException {
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.setProperty("java.class.path", tempDir.toString());
+ ClasspathLoader.main(new String[]{});
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithMultipleEntries(@TempDir Path tempDir) throws IOException {
+ File jarFile1 = createSimpleJar(tempDir, "test1.jar", "TestClass1");
+ File jarFile2 = createSimpleJar(tempDir, "test2.jar", "TestClass2");
+
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ String newClasspath = jarFile1.getAbsolutePath() +
+ File.pathSeparator +
+ jarFile2.getAbsolutePath();
+ System.setProperty("java.class.path", newClasspath);
+ ClasspathLoader.main(new String[]{});
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ @Test
+ void testLoadAllClassesWithBlocklistedClass(@TempDir Path tempDir) throws IOException {
+ File jarFile = tempDir.resolve("blocklist-test.jar").toFile();
+
+ try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
+ JarEntry blockedEntry = new JarEntry("META-INF/versions/9/module-info.class");
+ jos.putNextEntry(blockedEntry);
+ jos.write("dummy content".getBytes());
+ jos.closeEntry();
+
+ JarEntry normalEntry = new JarEntry("com/test/Normal.class");
+ jos.putNextEntry(normalEntry);
+ jos.write("dummy content".getBytes());
+ jos.closeEntry();
+ }
+
+ String originalClasspath = System.getProperty("java.class.path");
+ try {
+ System.setProperty("java.class.path", jarFile.getAbsolutePath());
+ ClasspathLoader.main(new String[]{});
+ // The test passes if no exception is thrown and the blocklisted class is skipped
+ } finally {
+ if (originalClasspath != null) {
+ System.setProperty("java.class.path", originalClasspath);
+ }
+ }
+ }
+
+ private File createSimpleJar(Path tempDir, String jarName, String className) throws IOException {
+ File jarFile = tempDir.resolve(jarName).toFile();
+
+ try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
+ // Add a simple non-class file to make it a valid jar
+ JarEntry entry = new JarEntry("com/test/" + className + ".txt");
+ jos.putNextEntry(entry);
+ jos.write("test content".getBytes());
+ jos.closeEntry();
+ }
+
+ return jarFile;
+ }
+}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/CustomerClassLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/CustomerClassLoaderTest.java
index 0169d0d6..71fb013f 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/CustomerClassLoaderTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/CustomerClassLoaderTest.java
@@ -1,4 +1,7 @@
-/* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
package com.amazonaws.services.lambda.runtime.api.client;
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfoTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfoTest.java
new file mode 100644
index 00000000..e134ddc8
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/HandlerInfoTest.java
@@ -0,0 +1,132 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class HandlerInfoTest {
+
+ @Test
+ void testConstructor() {
+ Class> testClass = String.class;
+ String methodName = "testMethod";
+
+ HandlerInfo info = new HandlerInfo(testClass, methodName);
+
+ assertNotNull(info);
+ assertEquals(testClass, info.clazz);
+ assertEquals(methodName, info.methodName);
+ }
+
+ @Test
+ void testFromStringWithoutMethod() throws Exception {
+ String handler = "java.lang.String";
+ HandlerInfo info = HandlerInfo.fromString(handler, ClassLoader.getSystemClassLoader());
+
+ assertEquals(String.class, info.clazz);
+ assertNull(info.methodName);
+ }
+
+ @Test
+ void testFromStringWithMethod() throws Exception {
+ String handler = "java.lang.String::length";
+ HandlerInfo info = HandlerInfo.fromString(handler, ClassLoader.getSystemClassLoader());
+
+ assertEquals(String.class, info.clazz);
+ assertEquals("length", info.methodName);
+ }
+
+ @Test
+ void testFromStringWithEmptyClass() {
+ String handler = "::method";
+
+ assertThrows(HandlerInfo.InvalidHandlerException.class, () ->
+ HandlerInfo.fromString(handler, ClassLoader.getSystemClassLoader())
+ );
+ }
+
+ @Test
+ void testFromStringWithEmptyMethod() {
+ String handler = "java.lang.String::";
+
+ assertThrows(HandlerInfo.InvalidHandlerException.class, () ->
+ HandlerInfo.fromString(handler, ClassLoader.getSystemClassLoader())
+ );
+ }
+
+ @Test
+ void testFromStringWithNonexistentClass() {
+ String handler = "com.nonexistent.TestClass::method";
+
+ assertThrows(ClassNotFoundException.class, () ->
+ HandlerInfo.fromString(handler, ClassLoader.getSystemClassLoader())
+ );
+ }
+
+ @Test
+ void testFromStringWithNullHandler() {
+ assertThrows(NullPointerException.class, () ->
+ HandlerInfo.fromString(null, ClassLoader.getSystemClassLoader())
+ );
+ }
+
+ @Test
+ void testClassNameWithoutMethod() {
+ String handler = "java.lang.String";
+ String className = HandlerInfo.className(handler);
+
+ assertEquals("java.lang.String", className);
+ }
+
+ @Test
+ void testClassNameWithMethod() {
+ String handler = "java.lang.String::length";
+ String className = HandlerInfo.className(handler);
+
+ assertEquals("java.lang.String", className);
+ }
+
+ @Test
+ void testClassNameWithEmptyString() {
+ String handler = "";
+ String className = HandlerInfo.className(handler);
+
+ assertEquals("", className);
+ }
+
+ @Test
+ void testClassNameWithOnlyDelimiter() {
+ String handler = "::";
+ String className = HandlerInfo.className(handler);
+
+ assertEquals("", className);
+ }
+
+ @Test
+ void testInvalidHandlerExceptionSerialVersionUID() {
+ assertEquals(-1L, HandlerInfo.InvalidHandlerException.serialVersionUID);
+ }
+
+ @Test
+ void testFromStringWithInnerClass() throws Exception {
+ // Create a custom class loader that can load our test class
+ ClassLoader cl = new ClassLoader() {
+ @Override
+ public Class> loadClass(String name) throws ClassNotFoundException {
+ if (name.equals("com.test.OuterClass$InnerClass")) {
+ throw new ClassNotFoundException("Test class not found");
+ }
+ return super.loadClass(name);
+ }
+ };
+
+ String handler = "com.test.OuterClass$InnerClass::method";
+ assertThrows(ClassNotFoundException.class, () ->
+ HandlerInfo.fromString(handler, cl)
+ );
+ }
+}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/LambdaRequestHandler.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/LambdaRequestHandler.java
new file mode 100644
index 00000000..d86b7385
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/LambdaRequestHandler.java
@@ -0,0 +1,142 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client;
+
+import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class LambdaRequestHandlerTest {
+
+ private InvocationRequest mockRequest;
+
+ @BeforeEach
+ void setUp() {
+ mockRequest = mock(InvocationRequest.class);
+ }
+
+ @Test
+ void testInitErrorHandler() {
+ String className = "com.example.TestClass";
+ Exception testException = new RuntimeException("initialization error");
+
+ LambdaRequestHandler handler = LambdaRequestHandler.initErrorHandler(testException, className);
+
+ assertNotNull(handler);
+ assertTrue(handler instanceof LambdaRequestHandler.UserFaultHandler);
+
+ LambdaRequestHandler.UserFaultHandler userFaultHandler = (LambdaRequestHandler.UserFaultHandler) handler;
+ UserFault fault = userFaultHandler.fault;
+
+ assertNotNull(fault);
+ assertEquals("Error loading class " + className + ": initialization error", fault.msg);
+ assertEquals("java.lang.RuntimeException", fault.exception);
+ assertTrue(fault.fatal);
+ }
+
+ @Test
+ void testClassNotFound() {
+ String className = "com.example.MissingClass";
+ Exception testException = new ClassNotFoundException("class not found");
+
+ LambdaRequestHandler handler = LambdaRequestHandler.classNotFound(testException, className);
+
+ assertNotNull(handler);
+ assertTrue(handler instanceof LambdaRequestHandler.UserFaultHandler);
+
+ LambdaRequestHandler.UserFaultHandler userFaultHandler = (LambdaRequestHandler.UserFaultHandler) handler;
+ UserFault fault = userFaultHandler.fault;
+
+ assertNotNull(fault);
+ assertEquals("Class not found: " + className, fault.msg);
+ assertEquals("java.lang.ClassNotFoundException", fault.exception);
+ assertFalse(fault.fatal);
+ }
+
+ @Test
+ void testUserFaultHandlerConstructor() {
+ UserFault testFault = new UserFault("test message", "TestException", "test trace");
+ LambdaRequestHandler.UserFaultHandler handler = new LambdaRequestHandler.UserFaultHandler(testFault);
+
+ assertNotNull(handler);
+ assertSame(testFault, handler.fault);
+ }
+
+ @Test
+ void testUserFaultHandlerCallThrowsFault() {
+ UserFault testFault = new UserFault("test message", "TestException", "test trace");
+ LambdaRequestHandler.UserFaultHandler handler = new LambdaRequestHandler.UserFaultHandler(testFault);
+
+ UserFault thrownFault = assertThrows(UserFault.class, () -> handler.call(mockRequest));
+ assertSame(testFault, thrownFault);
+ }
+
+ @Test
+ void testInitErrorHandlerWithNullMessage() {
+ String className = "com.example.TestClass";
+ Exception testException = new RuntimeException();
+
+ LambdaRequestHandler handler = LambdaRequestHandler.initErrorHandler(testException, className);
+
+ assertNotNull(handler);
+ assertTrue(handler instanceof LambdaRequestHandler.UserFaultHandler);
+
+ LambdaRequestHandler.UserFaultHandler userFaultHandler = (LambdaRequestHandler.UserFaultHandler) handler;
+ UserFault fault = userFaultHandler.fault;
+
+ assertNotNull(fault);
+ assertEquals("Error loading class " + className, fault.msg);
+ assertEquals("java.lang.RuntimeException", fault.exception);
+ assertTrue(fault.fatal);
+ }
+
+ @Test
+ void testInitErrorHandlerWithNullClassName() {
+ Exception testException = new RuntimeException("test error");
+
+ LambdaRequestHandler handler = LambdaRequestHandler.initErrorHandler(testException, null);
+
+ assertNotNull(handler);
+ assertTrue(handler instanceof LambdaRequestHandler.UserFaultHandler);
+
+ LambdaRequestHandler.UserFaultHandler userFaultHandler = (LambdaRequestHandler.UserFaultHandler) handler;
+ UserFault fault = userFaultHandler.fault;
+
+ assertNotNull(fault);
+ assertEquals("Error loading class null: test error", fault.msg);
+ assertEquals("java.lang.RuntimeException", fault.exception);
+ assertTrue(fault.fatal);
+ }
+
+ @Test
+ void testClassNotFoundWithNullClassName() {
+ Exception testException = new ClassNotFoundException("test error");
+
+ LambdaRequestHandler handler = LambdaRequestHandler.classNotFound(testException, null);
+
+ assertNotNull(handler);
+ assertTrue(handler instanceof LambdaRequestHandler.UserFaultHandler);
+
+ LambdaRequestHandler.UserFaultHandler userFaultHandler = (LambdaRequestHandler.UserFaultHandler) handler;
+ UserFault fault = userFaultHandler.fault;
+
+ assertNotNull(fault);
+ assertEquals("Class not found: null", fault.msg);
+ assertEquals("java.lang.ClassNotFoundException", fault.exception);
+ assertFalse(fault.fatal);
+ }
+
+ @Test
+ void testUserFaultHandlerCallWithNullRequest() {
+ UserFault testFault = new UserFault("test message", "TestException", "test trace");
+ LambdaRequestHandler.UserFaultHandler handler = new LambdaRequestHandler.UserFaultHandler(testFault);
+
+ UserFault thrownFault = assertThrows(UserFault.class, () -> handler.call(null));
+ assertSame(testFault, thrownFault);
+ }
+}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java
new file mode 100644
index 00000000..c2c88797
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java
@@ -0,0 +1,153 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client;
+
+import com.amazonaws.services.lambda.runtime.CustomPojoSerializer;
+import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class PojoSerializerLoaderTest {
+
+ @Mock
+ private CustomPojoSerializer mockSerializer;
+
+ @AfterEach
+ @BeforeEach
+ void setUp() throws Exception {
+ resetStaticFields();
+ }
+
+ private void resetStaticFields() throws Exception {
+ Field serializerField = PojoSerializerLoader.class.getDeclaredField("customPojoSerializer");
+ serializerField.setAccessible(true);
+ serializerField.set(null, null);
+
+ Field initializedField = PojoSerializerLoader.class.getDeclaredField("initialized");
+ initializedField.setAccessible(true);
+ initializedField.set(null, false);
+ }
+
+
+ private void setMockSerializer(CustomPojoSerializer serializer) throws Exception {
+ Field serializerField = PojoSerializerLoader.class.getDeclaredField("customPojoSerializer");
+ serializerField.setAccessible(true);
+ serializerField.set(null, serializer);
+ }
+
+ @Test
+ void testGetCustomerSerializerNoSerializerAvailable() throws Exception {
+ PojoSerializer