From 199c9621eaa214720f099e0c9cb9b5496d8b813e Mon Sep 17 00:00:00 2001
From: Maxime David
Date: Mon, 24 Mar 2025 14:56:05 +0000
Subject: [PATCH 01/49] misc: merge from public (#80)
---
.github/workflows/runtime-interface-client_pr.yml | 8 --------
.github/workflows/samples.yml | 2 +-
README.md | 4 ++--
aws-lambda-java-events/README.md | 2 +-
aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ----
aws-lambda-java-events/pom.xml | 2 +-
aws-lambda-java-runtime-interface-client/README.md | 4 ++--
.../RELEASE.CHANGELOG.md | 4 ----
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
.../runtime/api/client/PojoSerializerLoaderTest.java | 2 --
.../test/integration/test-handler/pom.xml | 2 +-
aws-lambda-java-tests/pom.xml | 2 +-
.../fastJson/HelloWorldFunction/pom.xml | 2 +-
.../custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +-
.../custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +-
.../request-stream-handler/HelloWorldFunction/pom.xml | 2 +-
samples/kinesis-firehose-event-handler/pom.xml | 2 +-
17 files changed, 15 insertions(+), 33 deletions(-)
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index 35c6ca06b..cbc5b5ab1 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -58,14 +58,6 @@ 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
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index 2b5e7833f..6d63f423e 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -42,7 +42,7 @@ jobs:
custom-serialization:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v4
# Set up both Java 8 and 21
- name: Set up Java 8 and 21
uses: actions/setup-java@v4
diff --git a/README.md b/README.md
index b5153a87f..7f4970949 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler {
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
```
@@ -163,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.7.0
+ 2.6.0
```
diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md
index 43c25d76a..87c61f345 100644
--- a/aws-lambda-java-events/README.md
+++ b/aws-lambda-java-events/README.md
@@ -74,7 +74,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
...
diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md
index a4bcd10a0..6c1769751 100644
--- a/aws-lambda-java-events/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md
@@ -1,7 +1,3 @@
-### 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))
diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml
index 925273e9b..f1364e7ab 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.16.1
+ 3.15.0
jar
AWS Lambda Java Events Library
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index 67a5972d6..368ab710a 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.7.0
+ 2.6.0
@@ -203,7 +203,7 @@ platform-specific JAR by setting the ``.
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.7.0
+ 2.6.0
linux-x86_64
```
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index ac073ae85..6a781b270 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,7 +1,3 @@
-### May 21, 2025
-`2.7.0`
-- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540))
-
### August 7, 2024
`2.6.0`
- Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime."
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index c854fabcd..fde515fda 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.4
+ 2.6.0
jar
AWS Lambda Java Runtime Interface Client
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
index c2c887973..7c6e9dcb4 100644
--- 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
@@ -7,7 +7,6 @@
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;
@@ -32,7 +31,6 @@ class PojoSerializerLoaderTest {
@Mock
private CustomPojoSerializer mockSerializer;
- @AfterEach
@BeforeEach
void setUp() throws Exception {
resetStaticFields();
diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
index 46d191a74..5304d44b9 100644
--- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
@@ -15,7 +15,7 @@
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.4
+ 2.6.0
diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml
index da07133c1..9e3419c69 100644
--- a/aws-lambda-java-tests/pom.xml
+++ b/aws-lambda-java-tests/pom.xml
@@ -45,7 +45,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.1
+ 3.15.0
org.junit.jupiter
diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
index 7d3c44246..7325c72a0 100644
--- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
index fd4271824..dd3b8e9c5 100644
--- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
com.google.code.gson
diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
index 2773ef1f1..f23214976 100644
--- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
index f6ca21bf7..68e7e81e9 100644
--- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
com.google.code.gson
diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml
index 56c959244..3d03205d3 100644
--- a/samples/kinesis-firehose-event-handler/pom.xml
+++ b/samples/kinesis-firehose-event-handler/pom.xml
@@ -46,7 +46,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.15.0
From a9c7fd1eea83423ed8d5887d31c588b433146be2 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Wed, 7 May 2025 11:00:21 +0100
Subject: [PATCH 02/49] Multi-concurrency mode for the Runtime Interface Client
and thread_local C++ Curl Client (#82)
Multi Concurrent RIC and Enable Curl Multi Concurrent Requests
---
.github/dependabot.yml | 4 +
.../lambda/runtime/api/client/AWSLambda.java | 69 +++--
.../api/client/EventHandlerLoader.java | 4 +-
.../api/client/PojoSerializerLoader.java | 2 +-
.../ReservedRuntimeEnvironmentVariables.java | 13 +
.../api/client/util/ConcurrencyConfig.java | 55 ++++
.../include/aws/lambda-runtime/runtime.h | 1 -
.../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 87 ++++---
.../runtime/api/client/AWSLambdaTest.java | 238 ++++++++++++++++++
.../client/util/ConcurrencyConfigTest.java | 94 +++++++
10 files changed, 502 insertions(+), 65 deletions(-)
create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3722537ae..34d287eec 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,5 +1,9 @@
version: 2
updates:
+ - package-ecosystem: "maven"
+ directory: "/aws-lambda-java-runtime-interface"
+ schedule:
+ interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
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 2eeb14e3d..fdd090077 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
@@ -19,6 +19,7 @@
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
+import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig;
import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream;
import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil;
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
@@ -35,6 +36,8 @@
import java.net.URLClassLoader;
import java.security.Security;
import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks:
@@ -49,8 +52,8 @@
*/
public class AWSLambda {
- protected static URLClassLoader customerClassLoader;
-
+ private static URLClassLoader customerClassLoader;
+
private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore";
private static final String JAVA_SECURITY_PROPERTIES = "java.security.properties";
@@ -68,9 +71,7 @@ public class AWSLambda {
private static final String INIT_TYPE_SNAP_START = "snap-start";
private static final String AWS_LAMBDA_INITIALIZATION_TYPE = System.getenv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_INITIALIZATION_TYPE);
-
- private static LambdaRuntimeApiClient runtimeClient;
-
+
static {
// Override the disabledAlgorithms setting to match configuration for openjdk8-u181.
// This is to keep DES ciphers around while we deploying security updates.
@@ -137,15 +138,13 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin
return requestHandler;
}
- private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException {
+ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) 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
@@ -167,13 +166,13 @@ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler
}
if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) {
- onInitComplete(lambdaLogger);
+ onInitComplete(lambdaLogger, runtimeClient);
}
return requestHandler;
}
- public static void setupRuntimeLogger(LambdaLogger lambdaLogger)
+ private static void setupRuntimeLogger(LambdaLogger lambdaLogger)
throws ClassNotFoundException {
ReflectUtil.setStaticField(
Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"),
@@ -213,10 +212,11 @@ private static LogSink createLogSink() {
}
public static void main(String[] args) throws Throwable {
- try (LambdaContextLogger logger = initLogger()) {
- LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger);
- startRuntimeLoop(lambdaRequestHandler, logger);
-
+ try (LambdaContextLogger lambdaLogger = initLogger()) {
+ LambdaRuntimeApiClient runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API);
+ LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], lambdaLogger, runtimeClient);
+ ConcurrencyConfig concurrencyConfig = new ConcurrencyConfig(lambdaLogger);
+ startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
} catch (IOException | ClassNotFoundException t) {
throw new Error(t);
}
@@ -232,7 +232,38 @@ private static LambdaContextLogger initLogger() {
return logger;
}
- private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable {
+ private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) {
+ executorService.submit(() -> {
+ try {
+ startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient);
+ } catch (Exception e) {
+ lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ }
+ });
+ }
+
+ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ConcurrencyConfig concurrencyConfig, LambdaRuntimeApiClient runtimeClient) throws Exception {
+ if (concurrencyConfig.isMultiConcurrent()) {
+ lambdaLogger.log(concurrencyConfig.getConcurrencyConfigMessage(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.INFO : LogLevel.UNDEFINED);
+ ExecutorService platformThreadExecutor = Executors.newFixedThreadPool(concurrencyConfig.getNumberOfPlatformThreads());
+ try {
+ for (int i = 0; i < concurrencyConfig.getNumberOfPlatformThreads(); i++) {
+ startRuntimeLoopWithExecutor(lambdaRequestHandler, lambdaLogger, platformThreadExecutor, runtimeClient);
+ }
+ } finally {
+ platformThreadExecutor.shutdown();
+ while (true) {
+ if (platformThreadExecutor.isTerminated()) {
+ break;
+ }
+ }
+ }
+ } else {
+ startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient);
+ }
+ }
+
+ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception {
boolean shouldExit = false;
while (!shouldExit) {
UserFault userFault = null;
@@ -245,7 +276,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda
ByteArrayOutputStream payload;
try {
- payload = requestHandler.call(request);
+ payload = lambdaRequestHandler.call(request);
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
// clear interrupted flag in case if it was set by user's code
Thread.interrupted();
@@ -275,7 +306,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda
}
}
- static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOException {
+ private static void onInitComplete(final LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws IOException {
try {
Core.getGlobalContext().beforeCheckpoint(null);
runtimeClient.restoreNext();
@@ -303,4 +334,8 @@ private static void logExceptionCloudWatch(LambdaContextLogger lambdaLogger, Exc
UserFault userFault = UserFault.makeUserFault(exc, true);
lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
}
+
+ protected static URLClassLoader getCustomerClassLoader() {
+ return customerClassLoader;
+ }
}
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 2876499ec..8fab27fed 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
@@ -116,7 +116,7 @@ private static PojoSerializer getSerializer(Platform platform, Type type
if (type instanceof Class) {
Class clazz = ((Class) type);
if (LambdaEventSerializers.isLambdaSupportedEvent(clazz.getName())) {
- return LambdaEventSerializers.serializerFor(clazz, AWSLambda.customerClassLoader);
+ return LambdaEventSerializers.serializerFor(clazz, AWSLambda.getCustomerClassLoader());
}
}
// else platform dependent (Android uses GSON but all other platforms use Jackson)
@@ -533,7 +533,7 @@ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStream
private void safeAddRequestIdToLog4j(String log4jContextClassName,
InvocationRequest request, Class contextMapValueClass) {
try {
- Class> log4jContextClass = ReflectUtil.loadClass(AWSLambda.customerClassLoader, log4jContextClassName);
+ Class> log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName);
log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass);
log4jContextPutMethod.call("AWSRequestId", request.getId());
} catch (Exception e) {
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java
index daea5911f..da37f7ca7 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java
@@ -28,7 +28,7 @@ private static CustomPojoSerializer loadSerializer()
return customPojoSerializer;
}
- ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.customerClassLoader);
+ ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.getCustomerClassLoader());
Iterator serializers = loader.iterator();
if (!serializers.hasNext()) {
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
index 7500a4943..2d32d4048 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
@@ -106,4 +106,17 @@ public interface ReservedRuntimeEnvironmentVariables {
* The environment's time zone (UTC). The execution environment uses NTP to synchronize the system clock.
*/
String TZ = "TZ";
+
+ /**
+ * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false".
+ * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops.
+ */
+ String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC";
+
+ /*
+ * Used to set the required number of concurrent runtime loops,
+ * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true",
+ * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores.
+ */
+ String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY";
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
new file mode 100644
index 000000000..db1f6c218
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
@@ -0,0 +1,55 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client.util;
+
+import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables;
+import com.amazonaws.services.lambda.runtime.api.client.UserFault;
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
+import com.amazonaws.services.lambda.runtime.logging.LogFormat;
+import com.amazonaws.services.lambda.runtime.logging.LogLevel;
+
+public class ConcurrencyConfig {
+ private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000;
+ private final int numberOfPlatformThreads;
+ private final boolean isConcurrencyEnabled;
+
+ public ConcurrencyConfig(LambdaContextLogger logger) {
+ this(logger, new EnvReader());
+ }
+
+ public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) {
+ this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC));
+ int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0;
+
+ if (this.isConcurrencyEnabled) {
+ try {
+ int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY));
+ if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) {
+ platformThreads = readNumOfPlatformThreads;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ } catch (Exception e) {
+ String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e));
+ logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
+ }
+ }
+
+ this.numberOfPlatformThreads = platformThreads;
+ }
+
+ public String getConcurrencyConfigMessage() {
+ return String.format("Starting %d concurrent function handler threads.", this.numberOfPlatformThreads);
+ }
+
+ public boolean isMultiConcurrent() {
+ return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2;
+ }
+
+ public int getNumberOfPlatformThreads() {
+ return numberOfPlatformThreads;
+ }
+}
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 d7db5f183..c4868c1ba 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
@@ -172,7 +172,6 @@ class runtime {
private:
std::string const m_user_agent_header;
std::array const m_endpoints;
- CURL* const m_curl_handle;
};
inline std::chrono::milliseconds invocation_request::get_time_remaining() const
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 eeaf0e7b9..e713da985 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,7 +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";
+thread_local static CURL* m_curl_handle = curl_easy_init();
enum Endpoints {
INIT,
@@ -163,63 +163,62 @@ runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cp
runtime::runtime(std::string const& endpoint, std::string const& user_agent)
: m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error",
endpoint + "/2018-06-01/runtime/invocation/next",
- endpoint + "/2018-06-01/runtime/invocation/"}},
- m_curl_handle(curl_easy_init())
+ endpoint + "/2018-06-01/runtime/invocation/"}}
{
- if (!m_curl_handle) {
+ if (!lambda_runtime::m_curl_handle) {
logging::log_error(LOG_TAG, "Failed to acquire curl easy handle for next.");
}
}
runtime::~runtime()
{
- curl_easy_cleanup(m_curl_handle);
+ curl_easy_cleanup(lambda_runtime::m_curl_handle);
}
void runtime::set_curl_next_options()
{
// lambda freezes the container when no further tasks are available. The freezing period could be longer than the
// request timeout, which causes the following get_next request to fail with a timeout error.
- curl_easy_reset(m_curl_handle);
- curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L);
- curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_reset(lambda_runtime::m_curl_handle);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
- curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str());
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str());
- curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data);
- curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header);
- curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, "");
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, "");
#ifndef NDEBUG
- curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback);
#endif
}
void runtime::set_curl_post_result_options()
{
- curl_easy_reset(m_curl_handle);
- curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L);
- curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_reset(lambda_runtime::m_curl_handle);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
- curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L);
- curl_easy_setopt(m_curl_handle, CURLOPT_READFUNCTION, read_data);
- curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data);
- curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_POST, 1L);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READFUNCTION, read_data);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header);
- curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, "");
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, "");
#ifndef NDEBUG
- curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback);
#endif
}
@@ -227,15 +226,15 @@ runtime::next_outcome runtime::get_next()
{
http::response resp;
set_curl_next_options();
- curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp);
- curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp);
curl_slist* headers = nullptr;
headers = curl_slist_append(headers, m_user_agent_header.c_str());
- curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers);
logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str());
- CURLcode curl_code = curl_easy_perform(m_curl_handle);
+ CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle);
logging::log_debug(LOG_TAG, "Completed request to %s", m_endpoints[Endpoints::NEXT].c_str());
curl_slist_free_all(headers);
@@ -247,13 +246,13 @@ runtime::next_outcome runtime::get_next()
{
long resp_code;
- curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code);
+ curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code);
resp.set_response_code(static_cast(resp_code));
}
{
char* content_type = nullptr;
- curl_easy_getinfo(m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type);
+ curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type);
resp.set_content_type(content_type);
}
@@ -327,7 +326,7 @@ runtime::post_outcome runtime::do_post(
invocation_response const& handler_response)
{
set_curl_post_result_options();
- curl_easy_setopt(m_curl_handle, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str());
logging::log_info(LOG_TAG, "Making request to %s", url.c_str());
curl_slist* headers = nullptr;
@@ -348,11 +347,11 @@ runtime::post_outcome runtime::do_post(
std::pair ctx{payload, 0};
aws::http::response resp;
- curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp);
- curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp);
- curl_easy_setopt(m_curl_handle, CURLOPT_READDATA, &ctx);
- curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers);
- CURLcode curl_code = curl_easy_perform(m_curl_handle);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READDATA, &ctx);
+ curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers);
+ CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle);
curl_slist_free_all(headers);
if (curl_code != CURLE_OK) {
@@ -366,7 +365,7 @@ runtime::post_outcome runtime::do_post(
}
long http_response_code;
- curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
+ curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
if (!is_success(aws::http::response_code(http_response_code))) {
logging::log_error(
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
new file mode 100644
index 000000000..4412214e9
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
@@ -0,0 +1,238 @@
+/*
+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 static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl;
+import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
+import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig;
+import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader;
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
+import com.amazonaws.services.lambda.runtime.logging.LogFormat;
+import com.amazonaws.services.lambda.runtime.logging.LogLevel;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+class AWSLambdaTest {
+
+ private static class SampleHandler implements RequestHandler, String> {
+ public static final String ADD_ENTRY_TO_MAP_ID_OP_MODE = "ADD_ENTRY_TO_MAP_ID";
+ public static final String FAIL_IMMEDIATELY_OP_MODE = "FAIL_IMMEDIATELY";
+
+ public static final int nOfIterations = 10;
+ public static final int perIterationDelayMS = 10;
+ public static Map hashMap = new ConcurrentHashMap();
+ public static AtomicInteger globalCounter = new AtomicInteger();
+
+ public static void resetStaticFields() {
+ hashMap.clear();
+ globalCounter = new AtomicInteger();
+ }
+
+ private static void addEntryToMapImplementation(String name) {
+ int i = 0;
+ while (i++ < nOfIterations) {
+ hashMap.put(name, hashMap.getOrDefault(name, 0) + 1);
+ globalCounter.incrementAndGet();
+ try {
+ Thread.sleep(perIterationDelayMS);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ @Override
+ public String handleRequest(Map event, Context context) {
+ // Thread.currentThread().getId() instead of Thread.currentThread().getName() when upgrading JAVA
+ String name = "Thread " + Thread.currentThread().getName();
+ String opMode = event.get("id");
+
+ switch (opMode) {
+ case ADD_ENTRY_TO_MAP_ID_OP_MODE:
+ addEntryToMapImplementation(name);
+ break;
+ case FAIL_IMMEDIATELY_OP_MODE:
+ String[] sArr = {};
+ return sArr[1];
+ default:
+ break;
+ }
+
+ return name;
+ }
+ }
+
+ @Mock
+ private LambdaRuntimeApiClientImpl runtimeClient;
+
+ @Mock
+ private LambdaContextLogger lambdaLogger;
+
+ @Mock
+ private EnvReader envReader;
+
+ @Mock
+ private ConcurrencyConfig concurrencyConfig;
+
+ private LambdaRequestHandler lambdaRequestHandler = new LambdaRequestHandler() {
+ private SampleHandler sHandler = new SampleHandler();
+
+ @Override
+ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception {
+ HashMap eventMap = new HashMap();
+ eventMap.put("id", request.getId());
+ sHandler.handleRequest(eventMap, null);
+ return new ByteArrayOutputStream();
+ }
+ };
+
+ private static InvocationRequest getFakeInvocationRequest(String id) {
+ InvocationRequest request = new InvocationRequest();
+ request.setId(id);
+ request.setDeadlineTimeInMs(Long.MAX_VALUE);
+ request.setContent("".getBytes());
+ return request;
+ }
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ SampleHandler.resetStaticFields();
+ }
+
+ /*
+ * com.amazonaws.services.lambda.runtime.api.client.util.SampleHandler contains static fields. These fields are expected to be shared if initialization is behaving as expected.
+ * After execution of the Runtime loops, we should see that the SampleHandler.globalCounter has been acted on by all the threads.
+ * The concurrent hashmap in SampleHandler.hashMap should also have all the correct count of Threads that ran.
+ * IMPORTANT: This test fails through only timeout.
+ */
+ @Test
+ @Timeout(value = 1, unit = TimeUnit.MINUTES)
+ void testConcurrentRunWithPlatformThreads() throws Throwable {
+ when(concurrencyConfig.isMultiConcurrent()).thenReturn(true);
+ when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4);
+
+ InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
+
+ when(runtimeClient.nextInvocation())
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(null)
+ .thenReturn(null)
+ .thenReturn(null)
+ .thenReturn(null);
+
+ AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
+
+ // Success Reports Must Equal number of tasks that ran successfully.
+ verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+ // Hashmap keys should equal the number of threads (runtime loops).
+ assertEquals(4, SampleHandler.hashMap.size());
+ // Hashmap total count should equal all tasks that ran * number of iterations per task
+ assertEquals(7 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
+ }
+
+ @Test
+ @Timeout(value = 1, unit = TimeUnit.MINUTES)
+ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(concurrencyConfig.isMultiConcurrent()).thenReturn(true);
+ when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4);
+
+ InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
+ InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE);
+ InvocationRequest userFaultRequest = mock(InvocationRequest.class);
+ final String UserFaultID = "Injected Fault Request ID";
+ when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
+
+ when(runtimeClient.nextInvocation())
+ .thenReturn(failImmediatelyRequest)
+ .thenReturn(userFaultRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(null)
+ .thenReturn(null)
+ .thenReturn(null);
+
+ AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
+
+ // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections.
+ verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR));
+
+ // Failed invokes should be reported.
+ verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
+ verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
+
+ // Success Reports Must Equal number of tasks that ran successfully.
+ verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+
+ // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully).
+ assertEquals(2, SampleHandler.hashMap.size());
+
+ // Hashmap total count should equal all tasks that ran * number of iterations per task
+ assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
+ }
+
+ @Test
+ @Timeout(value = 1, unit = TimeUnit.MINUTES)
+ void testSequentialWithFailures() throws Throwable {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(concurrencyConfig.isMultiConcurrent()).thenReturn(false);
+
+ InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
+ InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE);
+ InvocationRequest userFaultRequest = mock(InvocationRequest.class);
+ final String UserFaultID = "Injected Fault Request ID";
+ when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
+
+ when(runtimeClient.nextInvocation())
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(failImmediatelyRequest)
+ .thenReturn(userFaultRequest);
+
+ AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
+
+ // One for failImmediatelyRequest and userFaultRequest in finally block.
+ verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR));
+
+ // Failed invokes should be reported.
+ verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
+ verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
+
+ // Success Reports Must Equal number of tasks that ran successfully.
+ verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+
+ // Hashmap keys should equal one as it is not multithreaded.
+ assertEquals(1, SampleHandler.hashMap.size());
+
+ // Hashmap total count should equal all tasks that ran * number of iterations per task
+ assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
new file mode 100644
index 000000000..bbb43cd14
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
@@ -0,0 +1,94 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+
+package com.amazonaws.services.lambda.runtime.api.client.util;
+
+import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables;
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
+import com.amazonaws.services.lambda.runtime.logging.LogFormat;
+import com.amazonaws.services.lambda.runtime.logging.LogLevel;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class ConcurrencyConfigTest {
+ @Mock
+ private LambdaContextLogger lambdaLogger;
+
+ @Mock
+ private EnvReader envReader;
+
+ @Test
+ void testDefaultConfiguration() {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null);
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
+ assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ }
+
+ @Test
+ void testValidPlatformThreadsConfig() {
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ assertEquals(4, config.getNumberOfPlatformThreads());
+ assertEquals(true, config.isMultiConcurrent());
+ }
+
+ @Test
+ void testInvalidPlatformThreadsConfig() {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
+ verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
+ assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ }
+
+ @Test
+ void testExceedingPlatformThreadsLimit() {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
+ verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
+ assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ }
+
+ @Test
+ void testGetConcurrencyConfigMessage() {
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ String expectedMessage = "Starting 4 concurrent function handler threads.";
+ assertEquals(expectedMessage, config.getConcurrencyConfigMessage());
+ assertEquals(true, config.isMultiConcurrent());
+ }
+
+ @Test
+ void testGetConcurrencyConfigWithNoConcurrency() {
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ assertEquals(0, config.getNumberOfPlatformThreads());
+ assertEquals(false, config.isMultiConcurrent());
+ }
+}
From 2769f83b79ce0a83a388468c831c77bd79863b18 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Thu, 8 May 2025 13:29:35 +0100
Subject: [PATCH 03/49] Merge from Public Repo (#83)
* Merge from Public Repo
* Fix Failing Tests
---------
Co-authored-by: Mohammed Ehab
---
.../workflows/runtime-interface-client_merge_to_main.yml | 6 +-----
.../lambda/runtime/api/client/EventHandlerLoader.java | 1 -
.../lambda/runtime/api/client/api/LambdaContext.java | 7 -------
.../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 1 +
.../runtime/api/client/PojoSerializerLoaderTest.java | 2 ++
.../lambda/runtime/api/client/api/LambdaContextTest.java | 3 +--
.../runtime/api/client/logging/JsonLogFormatterTest.java | 2 --
7 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index 8909d56bf..e07b191e1 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@v5
+ - uses: actions/checkout@v4
- name: Set up JDK 1.8
uses: actions/setup-java@v4
@@ -47,10 +47,6 @@ 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
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 8fab27fed..af146fd93 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
@@ -582,7 +582,6 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep
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 20b77262d..bd1463db6 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
@@ -23,7 +23,6 @@ public class LambdaContext implements Context {
private final CognitoIdentity cognitoIdentity;
private final ClientContext clientContext;
private final String tenantId;
- private final String xrayTraceId;
private final LambdaLogger logger;
public LambdaContext(
@@ -37,7 +36,6 @@ public LambdaContext(
String functionVersion,
String invokedFunctionArn,
String tenantId,
- String xrayTraceId,
ClientContext clientContext
) {
this.memoryLimit = memoryLimit;
@@ -51,7 +49,6 @@ public LambdaContext(
this.functionVersion = functionVersion;
this.invokedFunctionArn = invokedFunctionArn;
this.tenantId = tenantId;
- this.xrayTraceId = xrayTraceId;
this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger();
}
@@ -101,10 +98,6 @@ 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/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 e713da985..3897d37db 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";
thread_local static CURL* m_curl_handle = curl_easy_init();
enum Endpoints {
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
index 7c6e9dcb4..4ebcf5d7e 100644
--- 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
@@ -8,6 +8,7 @@
import com.amazonaws.services.lambda.runtime.CustomPojoSerializer;
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -31,6 +32,7 @@ class PojoSerializerLoaderTest {
@Mock
private CustomPojoSerializer mockSerializer;
+ @AfterEach
@BeforeEach
void setUp() throws Exception {
resetStaticFields();
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
index f7da76198..58880be43 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
@@ -19,7 +19,6 @@ public class LambdaContextTest {
private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext();
public static final int MEMORY_LIMIT = 128;
public static final String TENANT_ID = "tenant-id";
- public static final String X_RAY_TRACE_ID = "x-ray-trace-id";
@Test
public void getRemainingTimeInMillis() {
@@ -56,6 +55,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException {
private LambdaContext createContextWithDeadline(long deadlineTimeInMs) {
return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME,
- FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT);
+ FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT);
}
}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
index 91ce9d2a3..531e9ca94 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
@@ -30,7 +30,6 @@ void testFormattingWithLambdaContext() {
null,
"function-arn",
null,
- null,
null
);
assertFormatsString("test log", LogLevel.WARN, context);
@@ -49,7 +48,6 @@ void testFormattingWithTenantIdInLambdaContext() {
null,
"function-arn",
"tenant-id",
- "xray-trace-id",
null
);
assertFormatsString("test log", LogLevel.WARN, context);
From a66ed4bdd0a461f486253c1f9adb9f20f5783f86 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Thu, 12 Jun 2025 14:17:36 +0100
Subject: [PATCH 04/49] Support Multiconcurrent Logging (#86)
* Support Multiconcurrent Logging
---------
Co-authored-by: Mohammed Ehab
---
.../api/client/logging/JsonLogFormatter.java | 16 ++++---
.../api/client/logging/StdOutLogSink.java | 2 +-
.../logging/AbstractLambdaLoggerTest.java | 43 ++++++++++++++++++-
3 files changed, 53 insertions(+), 8 deletions(-)
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 f463e7ee5..f1051a216 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
@@ -22,7 +22,7 @@ public class JsonLogFormatter implements LogFormatter {
withZone(ZoneId.of("UTC"));
private final PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class);
- private LambdaContext lambdaContext;
+ private ThreadLocal lambdaContext = new ThreadLocal<>();
@Override
public String format(String message, LogLevel logLevel) {
@@ -39,10 +39,12 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel)
msg.message = message;
msg.level = logLevel;
- if (lambdaContext != null) {
- msg.AWSRequestId = lambdaContext.getAwsRequestId();
- msg.tenantId = lambdaContext.getTenantId();
+ LambdaContext lambdaContextForCurrentThread = lambdaContext.get();
+ if (lambdaContextForCurrentThread != null) {
+ msg.AWSRequestId = lambdaContextForCurrentThread.getAwsRequestId();
+ msg.tenantId = lambdaContextForCurrentThread.getTenantId();
}
+
return msg;
}
@@ -53,6 +55,10 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel)
*/
@Override
public void setLambdaContext(LambdaContext context) {
- this.lambdaContext = context;
+ if (context == null) {
+ lambdaContext.remove();
+ } else {
+ lambdaContext.set(context);
+ }
}
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java
index 873e6fde5..90e7d39c2 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java
@@ -15,7 +15,7 @@ public void log(byte[] message) {
log(LogLevel.UNDEFINED, LogFormat.TEXT, message);
}
- public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) {
+ public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) {
try {
System.out.write(message);
} catch (IOException e) {
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
index baeb4c242..151e702af 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
@@ -5,10 +5,15 @@
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
import org.junit.jupiter.api.Test;
+import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import com.amazonaws.lambda.thirdparty.org.json.JSONObject;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
+import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext;
import com.amazonaws.services.lambda.runtime.logging.LogLevel;
@@ -20,12 +25,12 @@ public TestSink() {
}
@Override
- public void log(byte[] message) {
+ public synchronized void log(byte[] message) {
messages.add(message);
}
@Override
- public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) {
+ public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) {
messages.add(message);
}
@@ -62,6 +67,40 @@ public void testLoggingNullValuesWithoutLogLevelInText() {
assertEquals("null", new String(sink.getMessages().get(1)));
}
+ // Makes Sure Logging Contexts are thread local.
+ @Test
+ public void testMultiConcurrentLoggingWithoutLogLevelInJSON() {
+ TestSink sink = new TestSink();
+ LambdaContextLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON);
+
+ String someMessagePrefix = "Some Message from ";
+ String reqIDPrefix = "Thread ID as request# ";
+
+ final int nThreads = 5;
+ ExecutorService es = Executors.newFixedThreadPool(nThreads);
+ for (int i = 0; i < nThreads; i++) {
+ es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null)));
+ }
+
+ final int nMessages = 100_000;
+ for (int i = 0; i < nMessages; i++) {
+ es.submit(() -> logger.log(someMessagePrefix + Thread.currentThread().getName()));
+ }
+
+ es.shutdown();
+ while (!es.isTerminated()) {
+ ;
+ }
+
+ assertEquals(nMessages, sink.getMessages().size());
+ for (byte[] message : sink.getMessages()) {
+ JSONObject parsedLog = new JSONObject(new String(message, StandardCharsets.UTF_8));
+ String parsedMessage = parsedLog.getString("message");
+ String parsedReqID = parsedLog.getString("AWSRequestId");
+ assertEquals(parsedMessage.substring(someMessagePrefix.length()), parsedReqID.substring(reqIDPrefix.length()));
+ }
+ }
+
@Test
public void testLoggingNullValuesWithoutLogLevelInJSON() {
TestSink sink = new TestSink();
From 1fde59f45783a8bb150b4bd10fe3a4cccb563675 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Wed, 18 Jun 2025 15:24:33 +0100
Subject: [PATCH 05/49] Modified RIC Behavior for Retries and Failure Handling
(#84)
* Modified RIC Behavior for Retries and Failure Handling + Refactoring
---
.../lambda/runtime/api/client/AWSLambda.java | 97 ++++++-----
.../ReservedRuntimeEnvironmentVariables.java | 11 +-
.../lambda/runtime/api/client/UserFault.java | 2 +-
.../runtimeapi/LambdaRuntimeApiClient.java | 6 +
.../LambdaRuntimeApiClientImpl.java | 70 ++++++++
...timeClientMaxRetriesExceededException.java | 15 ++
.../api/client/util/ConcurrencyConfig.java | 30 ++--
.../runtime/api/client/AWSLambdaTest.java | 150 ++++++++++++++++--
.../logging/AbstractLambdaLoggerTest.java | 7 +-
.../LambdaRuntimeApiClientImplTest.java | 83 +++++++++-
.../client/util/ConcurrencyConfigTest.java | 29 ++--
11 files changed, 399 insertions(+), 101 deletions(-)
create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java
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 fdd090077..9c6dd78b1 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
@@ -15,6 +15,7 @@
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClient;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl;
+import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.RapidErrorType;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter;
@@ -235,7 +236,7 @@ private static LambdaContextLogger initLogger() {
private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) {
executorService.submit(() -> {
try {
- startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient);
+ startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, false);
} catch (Exception e) {
lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
}
@@ -259,49 +260,73 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle
}
}
} else {
- startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient);
+ startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, true);
}
}
- private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception {
+ private static LambdaError createLambdaErrorFromThrowableOrUserFault(Throwable t) {
+ if (t instanceof UserFault) {
+ return new LambdaError(
+ LambdaErrorConverter.fromUserFault((UserFault) t),
+ RapidErrorType.BadFunctionCode);
+ } else {
+ return new LambdaError(
+ LambdaErrorConverter.fromThrowable(t),
+ XRayErrorCauseConverter.fromThrowable(t),
+ RapidErrorType.UserException);
+ }
+ }
+
+ private static void setEnvVarForXrayTraceId(InvocationRequest request) {
+ if (request.getXrayTraceId() != null) {
+ System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId());
+ } else {
+ System.clearProperty(LAMBDA_TRACE_HEADER_PROP);
+ }
+ }
+
+ private static void reportNonLoopTerminatingException(LambdaContextLogger lambdaLogger, Throwable t) {
+ lambdaLogger.log(
+ String.format(
+ "Runtime Loop on Thread ID: %s Faced and Exception. This exception will not stop the runtime loop.\nException:\n%s",
+ Thread.currentThread().getName(), UserFault.trace(t)),
+ lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ }
+
+ /*
+ * In multiconcurrent mode (exitLoopOnErrors = false), The Runtime Loop will not exit unless LambdaRuntimeClientMaxRetriesExceededException is thrown when calling nextInvocationWithExponentialBackoff.
+ * In normal/sequential mode (exitLoopOnErrors = true), The Runtime Loop will exit if nextInvocation call fails, when UserFault is fatal, or an Error of type VirtualMachineError or IOError is thrown.
+ */
+ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient, boolean exitLoopOnErrors) throws Exception {
boolean shouldExit = false;
while (!shouldExit) {
- UserFault userFault = null;
- InvocationRequest request = runtimeClient.nextInvocation();
- if (request.getXrayTraceId() != null) {
- System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId());
- } else {
- System.clearProperty(LAMBDA_TRACE_HEADER_PROP);
- }
-
- ByteArrayOutputStream payload;
try {
- payload = lambdaRequestHandler.call(request);
- runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
- // clear interrupted flag in case if it was set by user's code
- Thread.interrupted();
- } catch (UserFault f) {
- shouldExit = f.fatal;
- userFault = f;
- UserFault.filterStackTrace(f);
- LambdaError error = new LambdaError(
- LambdaErrorConverter.fromUserFault(f),
- RapidErrorType.BadFunctionCode);
- runtimeClient.reportInvocationError(request.getId(), error);
+ UserFault userFault = null;
+ InvocationRequest request = exitLoopOnErrors ? runtimeClient.nextInvocation() : runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger);
+ setEnvVarForXrayTraceId(request);
+
+ try {
+ ByteArrayOutputStream payload = lambdaRequestHandler.call(request);
+ runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
+ // clear interrupted flag in case if it was set by user's code
+ Thread.interrupted();
+ } catch (Throwable t) {
+ UserFault.filterStackTrace(t);
+ userFault = UserFault.makeUserFault(t);
+ shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal);
+ LambdaError error = createLambdaErrorFromThrowableOrUserFault(t);
+ runtimeClient.reportInvocationError(request.getId(), error);
+ } finally {
+ if (userFault != null) {
+ lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ }
+ }
} catch (Throwable t) {
- shouldExit = t instanceof VirtualMachineError || t instanceof IOError;
- UserFault.filterStackTrace(t);
- userFault = UserFault.makeUserFault(t);
-
- LambdaError error = new LambdaError(
- LambdaErrorConverter.fromThrowable(t),
- XRayErrorCauseConverter.fromThrowable(t),
- RapidErrorType.UserException);
- runtimeClient.reportInvocationError(request.getId(), error);
- } finally {
- if (userFault != null) {
- lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ if (exitLoopOnErrors || t instanceof LambdaRuntimeClientMaxRetriesExceededException) {
+ throw t;
}
+
+ reportNonLoopTerminatingException(lambdaLogger, t);
}
}
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
index 2d32d4048..1de3dca99 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
@@ -107,16 +107,9 @@ public interface ReservedRuntimeEnvironmentVariables {
*/
String TZ = "TZ";
- /**
- * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false".
- * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops.
- */
- String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC";
-
/*
* Used to set the required number of concurrent runtime loops,
- * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true",
- * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores.
+ * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores.
*/
- String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY";
+ String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY";
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java
index c7c5c9ddf..7d8a50347 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java
@@ -38,7 +38,7 @@ public UserFault(String msg, String exception, String trace, Boolean fatal) {
* No more user code should run after a fault.
*/
public static UserFault makeUserFault(Throwable t) {
- return makeUserFault(t, false);
+ return t instanceof UserFault ? (UserFault) t : makeUserFault(t, false);
}
public static UserFault makeUserFault(Throwable t, boolean fatal) {
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java
index e2ae0969a..a62aeb9b8 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java
@@ -4,6 +4,7 @@
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import java.io.IOException;
@@ -24,6 +25,11 @@ public interface LambdaRuntimeApiClient {
*/
InvocationRequest nextInvocation() throws IOException;
+ /**
+ * Get next invocation with exponential backoff
+ */
+ InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception;
+
/**
* Report invocation success
* @param requestId request id
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java
index 65024b98e..caca69aa7 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java
@@ -4,7 +4,11 @@
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;
+import com.amazonaws.services.lambda.runtime.api.client.UserFault;
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
+import com.amazonaws.services.lambda.runtime.logging.LogFormat;
+import com.amazonaws.services.lambda.runtime.logging.LogLevel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -14,6 +18,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -30,6 +36,11 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient {
private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type";
// 1MiB
private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024;
+
+ // ~32 Seconds Max Backoff.
+ private static final long MAX_BACKOFF_PERIOD_MS = 1024 * 32;
+ private static final long INITIAL_BACKOFF_PERIOD_MS = 100;
+ private static final int MAX_NUMBER_OF_RETRIALS = 5;
private final String baseUrl;
private final String invocationEndpoint;
@@ -52,6 +63,65 @@ public InvocationRequest nextInvocation() {
return NativeClient.next();
}
+ /*
+ * Retry immediately then retry with exponential backoff.
+ */
+ public static T getSupplierResultWithExponentialBackoff(LambdaContextLogger lambdaLogger, long initialDelayMS, long maxBackoffPeriodMS, int maxNumOfAttempts, Supplier supplier, Function exceptionMessageComposer, Exception maxRetriesException) throws Exception {
+ long delayMS = initialDelayMS;
+ for (int attempts = 0; attempts < maxNumOfAttempts; attempts++) {
+ boolean isFirstAttempt = attempts == 0;
+ boolean isLastAttempt = (attempts + 1) == maxNumOfAttempts;
+
+ // Try and log whichever exceptions happened
+ try {
+ return supplier.get();
+ } catch (Exception e) {
+ String logMessage = exceptionMessageComposer.apply(e);
+ if (!isLastAttempt) {
+ logMessage += String.format("\nRetrying%s", isFirstAttempt ? "." : String.format(" in %d ms.", delayMS));
+ }
+
+ lambdaLogger.log(logMessage, lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ }
+
+ // throw if ran out of attempts.
+ if (isLastAttempt) {
+ throw maxRetriesException;
+ }
+
+ // update the delay duration.
+ if (!isFirstAttempt) {
+ try {
+ Thread.sleep(delayMS);
+ delayMS = Math.min(delayMS * 2, maxBackoffPeriodMS);
+ } catch (InterruptedException e) {
+ Thread.interrupted();
+ }
+ }
+ }
+
+ // Should Not be reached.
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception {
+ Supplier nextInvocationSupplier = () -> nextInvocation();
+ Function exceptionMessageComposer = (e) -> {
+ return String.format("Runtime Loop on Thread ID: %s Failed to fetch next invocation.\n%s", Thread.currentThread().getName(), UserFault.trace(e));
+ };
+
+ return getSupplierResultWithExponentialBackoff(
+ lambdaLogger,
+ INITIAL_BACKOFF_PERIOD_MS,
+ MAX_BACKOFF_PERIOD_MS,
+ MAX_NUMBER_OF_RETRIALS,
+ nextInvocationSupplier,
+ exceptionMessageComposer,
+ new LambdaRuntimeClientMaxRetriesExceededException("Get Next Invocation")
+ );
+ }
+
@Override
public void reportInvocationSuccess(String requestId, byte[] response) {
NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response);
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java
new file mode 100644
index 000000000..467afa25c
--- /dev/null
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java
@@ -0,0 +1,15 @@
+/*
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+*/
+package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;
+
+public class LambdaRuntimeClientMaxRetriesExceededException extends LambdaRuntimeClientException {
+ // 429 is possible; however, that is more appropriate when a server is responding to a spamming client that it wants to rate limit.
+ // In Our case, however, the RIC is a client that is not able to get a response from an upstream server, so 500 is more appropriate.
+ public LambdaRuntimeClientMaxRetriesExceededException(String operationName) {
+ super("Maximum Number of retries have been exceed" + (operationName.equals(null)
+ ? String.format(" for the %s operation.", operationName)
+ : "."), 500);
+ }
+}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
index db1f6c218..96fbd4b79 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
@@ -12,33 +12,27 @@
import com.amazonaws.services.lambda.runtime.logging.LogLevel;
public class ConcurrencyConfig {
- private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000;
+ private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000;
private final int numberOfPlatformThreads;
- private final boolean isConcurrencyEnabled;
public ConcurrencyConfig(LambdaContextLogger logger) {
this(logger, new EnvReader());
}
public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) {
- this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC));
- int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0;
-
- if (this.isConcurrencyEnabled) {
- try {
- int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY));
- if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) {
- platformThreads = readNumOfPlatformThreads;
- } else {
- throw new IllegalArgumentException();
- }
- } catch (Exception e) {
- String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e));
- logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
+ int readNumOfPlatformThreads = 0;
+ try {
+ readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY));
+ if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) {
+ throw new IllegalArgumentException();
}
+ } catch (Exception e) {
+ String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e));
+ logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
+ readNumOfPlatformThreads = 0;
}
- this.numberOfPlatformThreads = platformThreads;
+ this.numberOfPlatformThreads = readNumOfPlatformThreads;
}
public String getConcurrencyConfigMessage() {
@@ -46,7 +40,7 @@ public String getConcurrencyConfigMessage() {
}
public boolean isMultiConcurrent() {
- return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2;
+ return this.numberOfPlatformThreads > 1;
}
public int getNumberOfPlatformThreads() {
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
index 4412214e9..de1f9e73f 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java
@@ -12,6 +12,7 @@
import static org.mockito.Mockito.*;
import java.io.ByteArrayOutputStream;
+import java.io.IOError;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -19,6 +20,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl;
+import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig;
import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader;
@@ -115,6 +117,8 @@ private static InvocationRequest getFakeInvocationRequest(String id) {
return request;
}
+ private static final LambdaRuntimeClientMaxRetriesExceededException fakelambdaRuntimeClientMaxRetriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Fake max retries happened");
+
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
@@ -135,7 +139,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable {
InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
- when(runtimeClient.nextInvocation())
+ when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger))
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
@@ -143,10 +147,10 @@ void testConcurrentRunWithPlatformThreads() throws Throwable {
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
- .thenReturn(null)
- .thenReturn(null)
- .thenReturn(null)
- .thenReturn(null);
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException);
AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
@@ -171,20 +175,23 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable {
final String UserFaultID = "Injected Fault Request ID";
when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
- when(runtimeClient.nextInvocation())
+ when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger))
.thenReturn(failImmediatelyRequest)
.thenReturn(userFaultRequest)
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
- .thenReturn(null)
- .thenReturn(null)
- .thenReturn(null);
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException);
AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
- // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections.
- verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR));
-
+ // One for each of failImmediatelyRequest and userFaultRequest in finally block
+ // Four for crashing the Four runtime loops in the outermost catch of the runtime loop after the Null responses.
+ // 2 + 4 = 6
+ verify(lambdaLogger, times(6)).log(anyString(), eq(LogLevel.ERROR));
+
// Failed invokes should be reported.
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
@@ -198,24 +205,86 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable {
// Hashmap total count should equal all tasks that ran * number of iterations per task
assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
}
+
+ @Test
+ @Timeout(value = 1, unit = TimeUnit.MINUTES)
+ void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExceededException() throws Throwable {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(concurrencyConfig.isMultiConcurrent()).thenReturn(true);
+ when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(1);
+
+ InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
+ InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE);
+
+ InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
+ final String UserFaultID = "Injected Fault Request ID";
+ when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
+
+ InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
+ final String IOErrorID = "ioerr1";
+ when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID);
+
+ when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger))
+ .thenReturn(failImmediatelyRequest)
+ .thenReturn(userFaultRequest)
+ .thenReturn(virtualMachineErrorRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException)
+ .thenReturn(successfullInvocationRequest);
+
+ AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
+
+ // One for each of failImmediatelyRequest, userFaultRequest, and virtualMachineErrorRequest + One for the runtime loop thread crashing.
+ verify(lambdaLogger, times(4)).log(anyString(), eq(LogLevel.ERROR));
+
+ // Failed invokes should be reported.
+ verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
+ verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
+ verify(runtimeClient).reportInvocationError(eq(IOErrorID), any());
+
+ // Success Reports Must Equal number of tasks that ran successfully.
+ verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+
+ // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully).
+ assertEquals(1, SampleHandler.hashMap.size());
+
+ // Hashmap total count should equal all tasks that ran * number of iterations per task
+ assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
+ }
+
+
+ /*
+ *
+ * NON-CONCURRENT-MODE TESTS
+ *
+ */
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
- void testSequentialWithFailures() throws Throwable {
+ void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
when(concurrencyConfig.isMultiConcurrent()).thenReturn(false);
InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
- InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE);
- InvocationRequest userFaultRequest = mock(InvocationRequest.class);
+ InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes.
+
+ InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
final String UserFaultID = "Injected Fault Request ID";
when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
+
+ InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
+ final String IOErrorID = "ioerr1";
+ when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID);
when(runtimeClient.nextInvocation())
.thenReturn(successfullInvocationRequest)
.thenReturn(successfullInvocationRequest)
.thenReturn(failImmediatelyRequest)
- .thenReturn(userFaultRequest);
+ .thenReturn(userFaultRequest)
+ // these two should not be even feltched since userFaultRequest is not recoverable.
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(virtualMachineErrorRequest);
AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
@@ -226,8 +295,55 @@ void testSequentialWithFailures() throws Throwable {
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
- // Success Reports Must Equal number of tasks that ran successfully.
+ // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and userFaultRequest.
+ verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+ verify(runtimeClient, times(2)).reportInvocationError(any(), any());
+
+ // Hashmap keys should equal one as it is not multithreaded.
+ assertEquals(1, SampleHandler.hashMap.size());
+
+ // Hashmap total count should equal all tasks that ran * number of iterations per task
+ assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
+ }
+
+ @Test
+ @Timeout(value = 1, unit = TimeUnit.MINUTES)
+ void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(concurrencyConfig.isMultiConcurrent()).thenReturn(false);
+
+ InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE);
+ InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes.
+
+ InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
+ final String UserFaultID = "Injected Fault Request ID";
+ when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID);
+
+ InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode.
+ final String IOErrorID = "ioerr1";
+ when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID);
+
+ when(runtimeClient.nextInvocation())
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(failImmediatelyRequest)
+ .thenReturn(virtualMachineErrorRequest)
+ // these two should not be even feltched since userFaultRequest is not recoverable.
+ .thenReturn(successfullInvocationRequest)
+ .thenReturn(userFaultRequest);
+
+ AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);
+
+ // One for failImmediatelyRequest and userFaultRequest in finally block.
+ verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR));
+
+ // Failed invokes should be reported.
+ verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
+ verify(runtimeClient).reportInvocationError(eq(IOErrorID), any());
+
+ // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and virtualMachineErrorRequest.
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
+ verify(runtimeClient, times(2)).reportInvocationError(any(), any());
// Hashmap keys should equal one as it is not multithreaded.
assertEquals(1, SampleHandler.hashMap.size());
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
index 151e702af..f97602e37 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
@@ -67,7 +67,12 @@ public void testLoggingNullValuesWithoutLogLevelInText() {
assertEquals("null", new String(sink.getMessages().get(1)));
}
- // Makes Sure Logging Contexts are thread local.
+ /*
+ * Makes Sure Logging Contexts are thread local.
+ * We start `setLambdaContext` operations using the **single** shared `logger` object on a fixed thread pool, differentiating them with thread IDs.
+ * We then start concurrent `log` operations which are scheduled using that fixed pool.
+ * It is then verified that a given log operation, which logs the thread ID it is running on, used a context that had the same thread ID.
+ */
@Test
public void testMultiConcurrentLoggingWithoutLogLevelInJSON() {
TestSink sink = new TestSink();
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java
index 473e2aef3..710c1565e 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java
@@ -14,6 +14,8 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.StackElement;
@@ -22,8 +24,18 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import okhttp3.HttpUrl;
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
import static java.net.HttpURLConnection.HTTP_OK;
@@ -36,6 +48,14 @@
@DisabledOnOs(OS.MAC)
public class LambdaRuntimeApiClientImplTest {
+ @SuppressWarnings("rawtypes")
+ private final Supplier mockSupplier = mock(Supplier.class);
+ @SuppressWarnings("rawtypes")
+ private final Function mockExceptionMessageComposer = mock(Function.class);
+ private final LambdaContextLogger mockLambdaContextLogger = mock(LambdaContextLogger.class);
+ private final LambdaRuntimeClientMaxRetriesExceededException retriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Testing Invocations");
+ final String fakeExceptionMessage = "Something bad";
+
MockWebServer mockWebServer;
LambdaRuntimeApiClientImpl lambdaRuntimeApiClientImpl;
@@ -43,7 +63,7 @@ public class LambdaRuntimeApiClientImplTest {
ErrorRequest errorRequest = new ErrorRequest("testErrorMessage", "testErrorType", errorStackStrace);
String requestId = "1234";
-
+
@BeforeEach
void setUp() {
mockWebServer = new MockWebServer();
@@ -51,6 +71,67 @@ void setUp() {
lambdaRuntimeApiClientImpl = new LambdaRuntimeApiClientImpl(hostnamePort);
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testgetSupplierResultWithExponentialBackoffAllFailing() throws Exception {
+
+ when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage)));
+ when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage);
+
+ try {
+ LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException);
+ } catch (LambdaRuntimeClientMaxRetriesExceededException e) { }
+
+ verify(mockSupplier, times(5)).get();
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 10 ms."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 20 ms."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any());
+ verify(mockLambdaContextLogger, times(5)).log(anyString(), any());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testgetSupplierResultWithExponentialBackoffTwoFailingThenSuccess() throws Exception {
+ InvocationRequest fakeRequest = new InvocationRequest();
+
+ when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage);
+
+ when(mockSupplier.get())
+ .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage)))
+ .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage)))
+ .thenReturn(fakeRequest);
+
+ InvocationRequest invocationRequest = (InvocationRequest) LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException);
+
+ assertEquals(fakeRequest, invocationRequest);
+ verify(mockSupplier, times(3)).get();
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any());
+ verify(mockLambdaContextLogger, times(2)).log(anyString(), any());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testgetSupplierResultWithExponentialBackoffDoesntGoAboveMax() throws Exception {
+
+ when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage)));
+
+ when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage);
+
+ try {
+ LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 100, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException);
+ } catch (LambdaRuntimeClientMaxRetriesExceededException e) { }
+
+ verify(mockSupplier, times(5)).get();
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 100 ms."), any());
+ verify(mockLambdaContextLogger, times(2)).log(eq(fakeExceptionMessage + "\nRetrying in 200 ms."), any());
+ verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any());
+ verify(mockLambdaContextLogger, times(5)).log(anyString(), any());
+ }
+
@Test
public void reportInitErrorTest() {
try {
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
index bbb43cd14..ef1d832ec 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
@@ -30,18 +30,16 @@ class ConcurrencyConfigTest {
@Test
void testDefaultConfiguration() {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null);
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null);
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
- assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
- assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ assertEquals(0, config.getNumberOfPlatformThreads());
+ assertEquals(false, config.isMultiConcurrent());
}
@Test
void testValidPlatformThreadsConfig() {
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4");
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
assertEquals(4, config.getNumberOfPlatformThreads());
@@ -51,31 +49,28 @@ void testValidPlatformThreadsConfig() {
@Test
void testInvalidPlatformThreadsConfig() {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid");
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
- assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
+ assertEquals(0, config.getNumberOfPlatformThreads());
verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
- assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ assertEquals(false, config.isMultiConcurrent());
}
@Test
void testExceedingPlatformThreadsLimit() {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001");
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
- assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads());
+ assertEquals(0, config.getNumberOfPlatformThreads());
verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
- assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent());
+ assertEquals(false, config.isMultiConcurrent());
}
@Test
void testGetConcurrencyConfigMessage() {
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true");
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4");
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
String expectedMessage = "Starting 4 concurrent function handler threads.";
@@ -85,8 +80,6 @@ void testGetConcurrencyConfigMessage() {
@Test
void testGetConcurrencyConfigWithNoConcurrency() {
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false");
-
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
assertEquals(0, config.getNumberOfPlatformThreads());
assertEquals(false, config.isMultiConcurrent());
From 93221da3c6d2c74d7b69f91a12a99f7eaabc1360 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Thu, 26 Jun 2025 16:52:16 +0100
Subject: [PATCH 06/49] Merge from public repo (#88)
Merge from Public to Private Repo
---
.github/dependabot.yml | 1 -
README.md | 4 ++--
aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ----
aws-lambda-java-core/pom.xml | 2 +-
.../com/amazonaws/services/lambda/runtime/Context.java | 10 ----------
aws-lambda-java-events/README.md | 2 +-
aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++
aws-lambda-java-events/pom.xml | 2 +-
aws-lambda-java-runtime-interface-client/README.md | 4 ++--
.../RELEASE.CHANGELOG.md | 4 ++++
aws-lambda-java-runtime-interface-client/pom.xml | 4 ++--
aws-lambda-java-tests/pom.xml | 2 +-
.../fastJson/HelloWorldFunction/pom.xml | 2 +-
.../gson/HelloWorldFunction/pom.xml | 2 +-
.../moshi/HelloWorldFunction/pom.xml | 2 +-
.../request-stream-handler/HelloWorldFunction/pom.xml | 2 +-
samples/kinesis-firehose-event-handler/pom.xml | 2 +-
17 files changed, 23 insertions(+), 30 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 34d287eec..88f18ea29 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,7 +4,6 @@ updates:
directory: "/aws-lambda-java-runtime-interface"
schedule:
interval: "weekly"
-
- package-ecosystem: "github-actions"
directory: "/"
schedule:
diff --git a/README.md b/README.md
index 7f4970949..b5153a87f 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler {
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
```
@@ -163,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 aebc8ecd9..527e7dd46 100644
--- a/aws-lambda-java-core/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md
@@ -1,7 +1,3 @@
-### 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))
diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml
index cca9d0cdf..77245c9be 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.4.0
+ 1.3.0
jar
AWS Lambda Java Core Library
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 ed9311a11..152765df1 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
@@ -109,14 +109,4 @@ public interface Context {
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/README.md b/aws-lambda-java-events/README.md
index 87c61f345..43c25d76a 100644
--- a/aws-lambda-java-events/README.md
+++ b/aws-lambda-java-events/README.md
@@ -74,7 +74,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
...
diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md
index 6c1769751..a4bcd10a0 100644
--- a/aws-lambda-java-events/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### 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))
diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml
index f1364e7ab..8799966be 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.15.0
+ 3.16.0
jar
AWS Lambda Java Events Library
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index 368ab710a..67a5972d6 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.6.0
+ 2.7.0
@@ -203,7 +203,7 @@ platform-specific JAR by setting the ``.
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.6.0
+ 2.7.0
linux-x86_64
```
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index 6a781b270..ac073ae85 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### May 21, 2025
+`2.7.0`
+- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540))
+
### August 7, 2024
`2.6.0`
- Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime."
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index fde515fda..866520092 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.6.0
+ 2.7.0
jar
AWS Lambda Java Runtime Interface Client
@@ -61,7 +61,7 @@
com.amazonaws
aws-lambda-java-core
- 1.4.0
+ 1.3.0
com.amazonaws
diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml
index 9e3419c69..6589ca742 100644
--- a/aws-lambda-java-tests/pom.xml
+++ b/aws-lambda-java-tests/pom.xml
@@ -45,7 +45,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
org.junit.jupiter
diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
index 7325c72a0..7d3c44246 100644
--- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
index dd3b8e9c5..fd4271824 100644
--- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
com.google.code.gson
diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
index f23214976..2773ef1f1 100644
--- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
index 68e7e81e9..f6ca21bf7 100644
--- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
+++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml
@@ -20,7 +20,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
com.google.code.gson
diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml
index 3d03205d3..56c959244 100644
--- a/samples/kinesis-firehose-event-handler/pom.xml
+++ b/samples/kinesis-firehose-event-handler/pom.xml
@@ -46,7 +46,7 @@
com.amazonaws
aws-lambda-java-events
- 3.15.0
+ 3.16.0
From d7e2a6d188528ec22ade0e2ae8f6cd45fcdb8de5 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:49:55 +0100
Subject: [PATCH 07/49] Version Bump to 2.8 (#89)
Co-authored-by: Mohammed Ehab
---
README.md | 2 +-
aws-lambda-java-runtime-interface-client/README.md | 2 +-
aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
4 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index b5153a87f..4b22852c1 100644
--- a/README.md
+++ b/README.md
@@ -163,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.7.0
+ 2.8.0
```
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index 67a5972d6..512e66fe0 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.7.0
+ 2.8.0
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index ac073ae85..a030d288b 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### June 26, 2025
+`2.8.0`
+- Refactoring
+
### May 21, 2025
`2.7.0`
- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540))
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 866520092..a29be4279 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.7.0
+ 2.8.0
jar
AWS Lambda Java Runtime Interface Client
From 2cf626b817042b65016d4c8532259d8c5a819ff2 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Fri, 4 Jul 2025 18:39:45 +0100
Subject: [PATCH 08/49] Only Log Concurrency Warning Message when
AWS_LAMBDA_MAX_CONCURRENCY is set (#90)
* Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set
* Var name change
---------
Co-authored-by: Mohammed Ehab
---
.../api/client/util/ConcurrencyConfig.java | 19 ++++++++++++++-----
.../client/util/ConcurrencyConfigTest.java | 2 +-
2 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
index 96fbd4b79..d98ed71c8 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
@@ -22,14 +22,23 @@ public ConcurrencyConfig(LambdaContextLogger logger) {
public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) {
int readNumOfPlatformThreads = 0;
try {
- readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY));
- if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) {
- throw new IllegalArgumentException();
+ String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY);
+
+ // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently.
+ if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) {
+ readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar);
+ if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) {
+ throw new IllegalArgumentException();
+ }
}
} catch (Exception e) {
- String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e));
- logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
readNumOfPlatformThreads = 0;
+ String message = String.format(
+ "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.",
+ ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY,
+ AWS_LAMBDA_MAX_CONCURRENCY_LIMIT,
+ UserFault.trace(e));
+ logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
}
this.numberOfPlatformThreads = readNumOfPlatformThreads;
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
index ef1d832ec..248625e37 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
@@ -29,10 +29,10 @@ class ConcurrencyConfigTest {
@Test
void testDefaultConfiguration() {
- when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null);
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ verifyNoInteractions(lambdaLogger);
assertEquals(0, config.getNumberOfPlatformThreads());
assertEquals(false, config.isMultiConcurrent());
}
From e35157b9751492ea65a80716144205b248a500de Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Tue, 8 Jul 2025 10:05:09 +0100
Subject: [PATCH 09/49] Version Bump RIC to 2.8.1 (#91)
* Version Bump RIC to 2.8.1
---------
Authored-by: Mohammed Ehab
---
README.md | 2 +-
aws-lambda-java-runtime-interface-client/README.md | 2 +-
aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
4 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 4b22852c1..0870ec2b7 100644
--- a/README.md
+++ b/README.md
@@ -163,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.8.0
+ 2.8.1
```
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index 512e66fe0..90e2c0bb5 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.0
+ 2.8.1
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index a030d288b..daa8bde90 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### June 26, 2025
+`2.8.1`
+- Refactoring
+
### June 26, 2025
`2.8.0`
- Refactoring
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index a29be4279..92d9d0f40 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.0
+ 2.8.1
jar
AWS Lambda Java Runtime Interface Client
From ddb34451478eb57042fdd332b9caa2afd7a0bdf2 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Wed, 9 Jul 2025 17:08:43 +0100
Subject: [PATCH 10/49] Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash
the RIC Otherwise and Version Bump to 2.8.2 (#92)
* Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise.
* Version Bump to 2.8.2
---------
Authored-by: Mohammed Ehab
---
README.md | 2 +-
.../README.md | 2 +-
.../RELEASE.CHANGELOG.md | 4 ++
.../pom.xml | 2 +-
.../ReservedRuntimeEnvironmentVariables.java | 4 +-
.../api/client/util/ConcurrencyConfig.java | 19 ++++----
.../client/util/ConcurrencyConfigTest.java | 47 ++++++++++++++-----
7 files changed, 51 insertions(+), 29 deletions(-)
diff --git a/README.md b/README.md
index 0870ec2b7..70d7c7316 100644
--- a/README.md
+++ b/README.md
@@ -163,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.8.1
+ 2.8.2
```
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index 90e2c0bb5..e3c68fe8a 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.1
+ 2.8.2
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index daa8bde90..ff3d8bec5 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### June 26, 2025
+`2.8.2`
+- Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value.
+
### June 26, 2025
`2.8.1`
- Refactoring
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 92d9d0f40..59f33145e 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.1
+ 2.8.2
jar
AWS Lambda Java Runtime Interface Client
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
index 1de3dca99..9fdec6b9f 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java
@@ -108,8 +108,8 @@ public interface ReservedRuntimeEnvironmentVariables {
String TZ = "TZ";
/*
- * Used to set the required number of concurrent runtime loops,
- * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores.
+ * If set to a string parsable as an integer > 0, It enables multiconcurrency mode.
+ * Otherwise, if it is set to an invalid value, it will crash the whole RIC process.
*/
String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY";
}
diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
index d98ed71c8..7108c7801 100644
--- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
+++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java
@@ -14,6 +14,8 @@
public class ConcurrencyConfig {
private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000;
private final int numberOfPlatformThreads;
+ private final String INVALID_CONFIG_MESSAGE_PREFIX = String.format("User configured %s is invalid. Please make sure it is a positive number more than zero and less than or equal %d",
+ ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT);
public ConcurrencyConfig(LambdaContextLogger logger) {
this(logger, new EnvReader());
@@ -24,21 +26,16 @@ public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) {
try {
String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY);
- // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently.
- if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) {
+ if (readLambdaMaxConcurrencyEnvVar != null) {
readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar);
- if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) {
+ if (readNumOfPlatformThreads < 1 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) {
throw new IllegalArgumentException();
}
}
} catch (Exception e) {
- readNumOfPlatformThreads = 0;
- String message = String.format(
- "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.",
- ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY,
- AWS_LAMBDA_MAX_CONCURRENCY_LIMIT,
- UserFault.trace(e));
- logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED);
+ String message = String.format("%s\n%s", INVALID_CONFIG_MESSAGE_PREFIX, UserFault.trace(e));
+ logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
+ throw e;
}
this.numberOfPlatformThreads = readNumOfPlatformThreads;
@@ -49,7 +46,7 @@ public String getConcurrencyConfigMessage() {
}
public boolean isMultiConcurrent() {
- return this.numberOfPlatformThreads > 1;
+ return this.numberOfPlatformThreads >= 1;
}
public int getNumberOfPlatformThreads() {
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
index 248625e37..096d52bb0 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java
@@ -14,8 +14,9 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@@ -27,6 +28,8 @@ class ConcurrencyConfigTest {
@Mock
private EnvReader envReader;
+ private static final String exitingRuntimeString = String.format("User configured %s is invalid.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY);
+
@Test
void testDefaultConfiguration() {
when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null);
@@ -37,35 +40,51 @@ void testDefaultConfiguration() {
assertEquals(false, config.isMultiConcurrent());
}
+ @Test
+ void testBelowMinPlatformThreadsLimit() {
+ when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("0");
+
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader));
+ verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR));
+ }
+
+ @Test
+ void testMinValidPlatformThreadsConfig() {
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1");
+
+ ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ verifyNoInteractions(lambdaLogger);
+ assertEquals(1, config.getNumberOfPlatformThreads());
+ assertEquals(true, config.isMultiConcurrent());
+ }
+
@Test
void testValidPlatformThreadsConfig() {
when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4");
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ verifyNoInteractions(lambdaLogger);
assertEquals(4, config.getNumberOfPlatformThreads());
assertEquals(true, config.isMultiConcurrent());
}
@Test
- void testInvalidPlatformThreadsConfig() {
+ void testExceedingPlatformThreadsLimit() {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001");
- ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
- assertEquals(0, config.getNumberOfPlatformThreads());
- verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
- assertEquals(false, config.isMultiConcurrent());
+ assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader));
+ verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR));
}
@Test
- void testExceedingPlatformThreadsLimit() {
+ void testInvalidPlatformThreadsConfig() {
when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON);
- when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001");
+ when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid");
- ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
- assertEquals(0, config.getNumberOfPlatformThreads());
- verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN));
- assertEquals(false, config.isMultiConcurrent());
+ assertThrows(NumberFormatException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader));
+ verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR));
}
@Test
@@ -74,6 +93,7 @@ void testGetConcurrencyConfigMessage() {
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
String expectedMessage = "Starting 4 concurrent function handler threads.";
+ verifyNoInteractions(lambdaLogger);
assertEquals(expectedMessage, config.getConcurrencyConfigMessage());
assertEquals(true, config.isMultiConcurrent());
}
@@ -81,6 +101,7 @@ void testGetConcurrencyConfigMessage() {
@Test
void testGetConcurrencyConfigWithNoConcurrency() {
ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader);
+ verifyNoInteractions(lambdaLogger);
assertEquals(0, config.getNumberOfPlatformThreads());
assertEquals(false, config.isMultiConcurrent());
}
From 8fe3580bbb419c3adfc6de99abe5fd27fdbf8720 Mon Sep 17 00:00:00 2001
From: Astraea Quinn Sinclair
Date: Tue, 15 Jul 2025 09:08:43 +0100
Subject: [PATCH 11/49] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?=
=?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?=
=?UTF-8?q?pendencies?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6
- Bump aws-lambda-java-events from 3.16.0 to 3.16.1
- Update aws-lambda-java-tests dependencies to use new versions
- Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2โ1.1.6
- Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2โ3.16.1
---
aws-lambda-java-events/pom.xml | 2 +-
aws-lambda-java-tests/pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml
index 8799966be..925273e9b 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.16.0
+ 3.16.1
jar
AWS Lambda Java Events Library
diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml
index 6589ca742..da07133c1 100644
--- a/aws-lambda-java-tests/pom.xml
+++ b/aws-lambda-java-tests/pom.xml
@@ -45,7 +45,7 @@
com.amazonaws
aws-lambda-java-events
- 3.16.0
+ 3.16.1
org.junit.jupiter
From 428d1255901ba2280ba533bcc90b98f6c46611a8 Mon Sep 17 00:00:00 2001
From: Astraea Quinn Sinclair
Date: Tue, 15 Jul 2025 09:26:46 +0100
Subject: [PATCH 12/49] Fix runtime interface client workflows to use local
serialization dependency
Add local build step for aws-lambda-java-serialization before building runtime interface client.
## Why This Fix is Needed
The runtime interface client depends on aws-lambda-java-serialization version 1.1.6,
but this version doesn't exist in Maven Central yet. By building and installing the
serialization package locally first, we ensure:
1. The correct version (1.1.6) is available in the local Maven repository
2. The runtime interface client build won't fail looking for a non-existent version on Maven Central
3. The workflow tests the actual code changes together
## Changes Made
- runtime-interface-client_merge_to_main.yml: Added local serialization build step
- runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs
This ensures CI/CD pipeline works correctly with the new dependency versions.
---
.../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++
.github/workflows/runtime-interface-client_pr.yml | 8 ++++++++
2 files changed, 12 insertions(+)
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index e07b191e1..88f8afde2 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -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
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index cbc5b5ab1..d1821d2ce 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -31,6 +31,10 @@ jobs:
working-directory: ./aws-lambda-java-serialization
run: mvn clean install -DskipTests
+ - 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
run: make pr
@@ -59,6 +63,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
From 477c0805ed9247d9c6f7abc567d68f6693ce7826 Mon Sep 17 00:00:00 2001
From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com>
Date: Wed, 16 Jul 2025 13:12:48 +0100
Subject: [PATCH 13/49] Update ric pom.xml
---
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 59f33145e..7a0d0264a 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.2
+ 2.8.3
jar
AWS Lambda Java Runtime Interface Client
From 44cd05e502c4717400a398a4a6b9276044588b6c Mon Sep 17 00:00:00 2001
From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com>
Date: Thu, 17 Jul 2025 13:41:48 +0100
Subject: [PATCH 14/49] Update runtime-interface-client_merge_to_main.yml
---
.github/workflows/runtime-interface-client_merge_to_main.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index 88f8afde2..94294501c 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -49,7 +49,7 @@ jobs:
- name: Build and install serialization dependency locally
working-directory: ./aws-lambda-java-serialization
- run: mvn clean install -DskipTests
+ run: mvn clean install
- name: Test Runtime Interface Client xplatform build - Run 'build' target
working-directory: ./aws-lambda-java-runtime-interface-client
From ba4ef1c0862e5997ac9764bf07a7e108d11bece3 Mon Sep 17 00:00:00 2001
From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com>
Date: Thu, 17 Jul 2025 14:02:05 +0100
Subject: [PATCH 15/49] Update runtime-interface-client_pr.yml
---
.github/workflows/runtime-interface-client_pr.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index d1821d2ce..b5631d2b4 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -33,7 +33,7 @@ jobs:
- name: Build and install serialization dependency locally
working-directory: ./aws-lambda-java-serialization
- run: mvn clean install -DskipTests
+ run: mvn clean install
- name: Runtime Interface Client smoke tests - Run 'pr' target
working-directory: ./aws-lambda-java-runtime-interface-client
@@ -65,7 +65,7 @@ jobs:
- name: Build and install serialization dependency locally
working-directory: ./aws-lambda-java-serialization
- run: mvn clean install -DskipTests
+ run: mvn clean install
- name: Test Runtime Interface Client xplatform build - Run 'build' target
working-directory: ./aws-lambda-java-runtime-interface-client
From 3ac07d05773a299bfc10faa0dd0ce3e854f07af4 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Fri, 25 Jul 2025 18:06:33 +0100
Subject: [PATCH 16/49] Ensure EventHandlerLoader Thread Safety. (#95)
* Make handler response buffers thread safe.
* Add multiconcurrency tests
* ThreadLocal instead of Allocating new buffers every invoke.
* Thread local log4jContextPutMethod
* Fix indentations
* Add CountDownLatch to ensure all calls are done simultaneously.
---------
Co-authored-by: Mohammed Ehab
---
.../api/client/EventHandlerLoader.java | 37 ++++-----
.../api/client/EventHandlerLoaderTest.java | 79 ++++++++++++++++++-
2 files changed, 96 insertions(+), 20 deletions(-)
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 af146fd93..afb3e3df7 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
@@ -57,10 +57,10 @@ private enum Platform {
UNKNOWN
}
- private static volatile PojoSerializer contextSerializer;
- private static volatile PojoSerializer cognitoSerializer;
+ private static volatile ThreadLocal> contextSerializer = new ThreadLocal<>();
+ private static volatile ThreadLocal> cognitoSerializer = new ThreadLocal<>();
- private static final EnumMap>> typeCache = new EnumMap<>(Platform.class);
+ private static final ThreadLocal>>> typeCache = ThreadLocal.withInitial(() -> new EnumMap<>(Platform.class));
private static final Comparator methodPriority = new Comparator() {
public int compare(Method lhs, Method rhs) {
@@ -127,10 +127,11 @@ private static PojoSerializer getSerializer(Platform platform, Type type
}
private static PojoSerializer getSerializerCached(Platform platform, Type type) {
- Map> cache = typeCache.get(platform);
+ EnumMap>> threadTypeCache = typeCache.get();
+ Map> cache = threadTypeCache.get(platform);
if (cache == null) {
cache = new HashMap<>();
- typeCache.put(platform, cache);
+ threadTypeCache.put(platform, cache);
}
PojoSerializer serializer = cache.get(type);
@@ -143,17 +144,17 @@ private static PojoSerializer getSerializerCached(Platform platform, Typ
}
private static PojoSerializer getContextSerializer() {
- if (contextSerializer == null) {
- contextSerializer = GsonFactory.getInstance().getSerializer(LambdaClientContext.class);
+ if (contextSerializer.get() == null) {
+ contextSerializer.set(GsonFactory.getInstance().getSerializer(LambdaClientContext.class));
}
- return contextSerializer;
+ return contextSerializer.get();
}
private static PojoSerializer getCognitoSerializer() {
- if (cognitoSerializer == null) {
- cognitoSerializer = GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class);
+ if (cognitoSerializer.get() == null) {
+ cognitoSerializer.set(GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class));
}
- return cognitoSerializer;
+ return cognitoSerializer.get();
}
@@ -527,15 +528,14 @@ private static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Typ
private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) {
return new LambdaRequestHandler() {
- private final ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
- private Functions.V2 log4jContextPutMethod = null;
+ private final ThreadLocal outputBuffers = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(1024));
+ private ThreadLocal> log4jContextPutMethod = new ThreadLocal<>();
- private void safeAddRequestIdToLog4j(String log4jContextClassName,
- InvocationRequest request, Class contextMapValueClass) {
+ private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) {
try {
Class> log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName);
- log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass);
- log4jContextPutMethod.call("AWSRequestId", request.getId());
+ log4jContextPutMethod.set(ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass));
+ log4jContextPutMethod.get().call("AWSRequestId", request.getId());
} catch (Exception e) {
// nothing to do here
}
@@ -558,6 +558,7 @@ private void safeAddContextToLambdaLogger(LambdaContext context) {
}
public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception {
+ ByteArrayOutputStream output = outputBuffers.get();
output.reset();
LambdaCognitoIdentity cognitoIdentity = null;
@@ -591,7 +592,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep
safeAddRequestIdToLog4j("org.apache.log4j.MDC", request, Object.class);
safeAddRequestIdToLog4j("org.apache.logging.log4j.ThreadContext", request, String.class);
// if put method not assigned in either call to safeAddRequestIdtoLog4j then log4jContextPutMethod = null
- if (log4jContextPutMethod == null) {
+ if (log4jContextPutMethod.get() == null) {
System.err.println("Customer using log4j appender but unable to load either "
+ "org.apache.log4j.MDC or org.apache.logging.log4j.ThreadContext. "
+ "Customer cannot see RequestId in log4j log lines.");
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java
index 76e6f0249..aae2f1afe 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java
@@ -4,8 +4,16 @@
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class EventHandlerLoaderTest {
@@ -37,7 +45,6 @@ void PojoHandlerTest_oneParamEvent() throws Exception {
assertSuccessfulInvocation(lambdaRequestHandler);
}
-
@Test
void PojoHandlerTest_oneParamContext() throws Exception {
String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context";
@@ -74,4 +81,72 @@ private static InvocationRequest getTestInvocationRequest() {
invocationRequest.setXrayTraceId("traceId");
return invocationRequest;
}
-}
\ No newline at end of file
+
+ // Multithreaded test methods
+
+ @Test
+ void RequestHandlerTest_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.RequestHandlerImpl");
+ }
+
+ @Test
+ void RequestStreamHandlerTest_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.RequestStreamHandlerImpl");
+ }
+
+ @Test
+ void PojoHandlerTest_noParams_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::noParamsHandler");
+ }
+
+ @Test
+ void PojoHandlerTest_oneParamEvent_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_event");
+ }
+
+ @Test
+ void PojoHandlerTest_oneParamContext_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context");
+ }
+
+ @Test
+ void PojoHandlerTest_twoParams_Multithreaded() throws Exception {
+ testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::twoParamsHandler");
+ }
+
+ private void testHandlerConcurrency(String handlerName) throws Exception {
+ // Create one handler instance
+ LambdaRequestHandler handler = getLambdaRequestHandler(handlerName);
+
+ int threadCount = 10;
+ ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+ List> futures = new ArrayList<>();
+ CountDownLatch startLatch = new CountDownLatch(1);
+
+ try {
+ for (int i = 0; i < threadCount; i++) {
+ futures.add(executor.submit(() -> {
+ try {
+ InvocationRequest request = getTestInvocationRequest();
+ startLatch.await();
+ ByteArrayOutputStream result = handler.call(request);
+ return result.toString();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+
+ // Release all threads simultaneously and Verify all invocations return the expected result
+ startLatch.countDown();
+
+ for (Future future : futures) {
+ String result = future.get(5, TimeUnit.SECONDS);
+ assertEquals("\"success\"", result);
+ }
+ } finally {
+ executor.shutdown();
+ assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
+ }
+ }
+}
From 1a7af454aa66eb4ea095c65de1f977c8f0dc26a0 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Wed, 3 Sep 2025 19:43:27 +0100
Subject: [PATCH 17/49] Make Trace ID accessible through Context (#101)
* Make Trace ID accessible through Context
* Change Constructor
---------
Co-authored-by: Mohammed Ehab
---
.github/workflows/runtime-interface-client_pr.yml | 8 ++++----
aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++
aws-lambda-java-core/pom.xml | 2 +-
.../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
.../lambda/runtime/api/client/EventHandlerLoader.java | 1 +
.../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++
.../runtime/api/client/api/LambdaContextTest.java | 3 ++-
.../api/client/logging/AbstractLambdaLoggerTest.java | 2 +-
.../api/client/logging/JsonLogFormatterTest.java | 2 ++
10 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index b5631d2b4..5819218c9 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -27,10 +27,6 @@ jobs:
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: Build and install serialization dependency locally
working-directory: ./aws-lambda-java-serialization
run: mvn clean install
@@ -62,6 +58,10 @@ 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
diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md
index 527e7dd46..aebc8ecd9 100644
--- a/aws-lambda-java-core/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md
@@ -1,3 +1,7 @@
+### 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))
diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml
index 77245c9be..cca9d0cdf 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.3.0
+ 1.4.0
jar
AWS Lambda Java Core Library
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 152765df1..ed9311a11 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
@@ -109,4 +109,14 @@ public interface Context {
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-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 7a0d0264a..189743054 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -61,7 +61,7 @@
com.amazonaws
aws-lambda-java-core
- 1.3.0
+ 1.4.0
com.amazonaws
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 afb3e3df7..f679c217c 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
@@ -583,6 +583,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep
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 bd1463db6..20b77262d 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
@@ -23,6 +23,7 @@ public class LambdaContext implements Context {
private final CognitoIdentity cognitoIdentity;
private final ClientContext clientContext;
private final String tenantId;
+ private final String xrayTraceId;
private final LambdaLogger logger;
public LambdaContext(
@@ -36,6 +37,7 @@ public LambdaContext(
String functionVersion,
String invokedFunctionArn,
String tenantId,
+ String xrayTraceId,
ClientContext clientContext
) {
this.memoryLimit = memoryLimit;
@@ -49,6 +51,7 @@ public LambdaContext(
this.functionVersion = functionVersion;
this.invokedFunctionArn = invokedFunctionArn;
this.tenantId = tenantId;
+ this.xrayTraceId = xrayTraceId;
this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger();
}
@@ -98,6 +101,10 @@ 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/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
index 58880be43..f7da76198 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java
@@ -19,6 +19,7 @@ public class LambdaContextTest {
private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext();
public static final int MEMORY_LIMIT = 128;
public static final String TENANT_ID = "tenant-id";
+ public static final String X_RAY_TRACE_ID = "x-ray-trace-id";
@Test
public void getRemainingTimeInMillis() {
@@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException {
private LambdaContext createContextWithDeadline(long deadlineTimeInMs) {
return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME,
- FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT);
+ FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT);
}
}
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
index f97602e37..3a5ee8d5f 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java
@@ -84,7 +84,7 @@ public void testMultiConcurrentLoggingWithoutLogLevelInJSON() {
final int nThreads = 5;
ExecutorService es = Executors.newFixedThreadPool(nThreads);
for (int i = 0; i < nThreads; i++) {
- es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null)));
+ es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null, null)));
}
final int nMessages = 100_000;
diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
index 531e9ca94..91ce9d2a3 100644
--- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
+++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java
@@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() {
null,
"function-arn",
null,
+ null,
null
);
assertFormatsString("test log", LogLevel.WARN, context);
@@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() {
null,
"function-arn",
"tenant-id",
+ "xray-trace-id",
null
);
assertFormatsString("test log", LogLevel.WARN, context);
From e9c084ea084f26b5aed8bcae9d466b20dc7149ba Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Tue, 9 Sep 2025 11:46:22 +0100
Subject: [PATCH 18/49] Migrate to Deploying using Maven Central (#103)
Co-authored-by: Mohammed Ehab
---
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 189743054..c854fabcd 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.3
+ 2.8.4
jar
AWS Lambda Java Runtime Interface Client
From 2250e99a2ed537a2a3dde63375944ce4b70e28ff Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Mon, 15 Sep 2025 13:30:26 +0100
Subject: [PATCH 19/49] Fix performance Issue By Using Blocking Calls instead
of Busy Waiting (#106)
Co-authored-by: Mohammed Ehab
---
.../services/lambda/runtime/api/client/AWSLambda.java | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
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 9c6dd78b1..10d595c7b 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
@@ -39,6 +39,7 @@
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
/**
* The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks:
@@ -253,10 +254,10 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle
}
} finally {
platformThreadExecutor.shutdown();
- while (true) {
- if (platformThreadExecutor.isTerminated()) {
- break;
- }
+ try {
+ platformThreadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
}
} else {
From 085c9754f497b6ba3240bea3a8cef3286ba823b3 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Mon, 15 Sep 2025 13:30:55 +0100
Subject: [PATCH 20/49] Log errorType and errorMessage from RAPID (#105)
Co-authored-by: Mohammed Ehab
---
.../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 3897d37db..84a84b439 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
@@ -370,7 +370,7 @@ runtime::post_outcome runtime::do_post(
if (!is_success(aws::http::response_code(http_response_code))) {
logging::log_error(
- LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code);
+ LOG_TAG, "Failed to post handler success response. Http response code: %ld. %s", http_response_code, resp.get_body().c_str());
return aws::http::response_code(http_response_code);
}
From e61bc6342a6d731e8699f3e8e558e4664a1249a0 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Wed, 17 Sep 2025 15:52:42 +0100
Subject: [PATCH 21/49] Version Bump RIC to 2.8.5 (#107)
Co-authored-by: Mohammed Ehab
---
README.md | 2 +-
aws-lambda-java-runtime-interface-client/README.md | 4 ++--
.../RELEASE.CHANGELOG.md | 13 +++++++++++++
aws-lambda-java-runtime-interface-client/pom.xml | 2 +-
.../test/integration/test-handler/pom.xml | 2 +-
5 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 70d7c7316..609485625 100644
--- a/README.md
+++ b/README.md
@@ -163,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.8.2
+ 2.8.5
```
diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md
index e3c68fe8a..c63dcfeef 100644
--- a/aws-lambda-java-runtime-interface-client/README.md
+++ b/aws-lambda-java-runtime-interface-client/README.md
@@ -70,7 +70,7 @@ pom.xml
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.2
+ 2.8.5
@@ -203,7 +203,7 @@ platform-specific JAR by setting the ``.
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.7.0
+ 2.8.5
linux-x86_64
```
diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
index ff3d8bec5..a8c34dec8 100644
--- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
@@ -1,3 +1,16 @@
+### September 17, 2025
+`2.8.5`
+- Log errorType and errorMessage from RAPID in C++ Client.
+- Performance Upgrade for Multiconcurrency Mode.
+
+### September 9, 2025
+`2.8.4`
+- Make Trace ID Accessible through Context Object.
+
+### July 19, 2025
+`2.8.3`
+- Ensure EventHandlerLoader Thread Safety.
+
### June 26, 2025
`2.8.2`
- Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value.
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index c854fabcd..b77c38e00 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.8.4
+ 2.8.5
jar
AWS Lambda Java Runtime Interface Client
diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
index 5304d44b9..35ea694ab 100644
--- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml
@@ -15,7 +15,7 @@
com.amazonaws
aws-lambda-java-runtime-interface-client
- 2.6.0
+ 2.8.5
From f93664f998fcf3c52787fae1dc2bceaf639c2f82 Mon Sep 17 00:00:00 2001
From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com>
Date: Mon, 22 Sep 2025 13:13:11 +0100
Subject: [PATCH 22/49] MultiConcurrent XRAY TraceID using utils-lite (#96)
Co-authored-by: Mohammed Ehab
---
.../pom.xml | 38 +--
.../lambda/runtime/api/client/AWSLambda.java | 11 +-
.../runtime/api/client/AWSLambdaTest.java | 230 +++++++++++++++++-
3 files changed, 258 insertions(+), 21 deletions(-)
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index b77c38e00..0d3e7fb04 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -1,6 +1,6 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
com.amazonaws
aws-lambda-java-runtime-interface-client
@@ -47,9 +47,9 @@
separately from the Runtime Interface Client functionality until we figure something else out.
-->
true
-
-
-
+
+
+
4.0.0
@@ -40,7 +41,7 @@
com.amazonaws
aws-lambda-java-serialization
- 1.3.0
+ 1.2.0
com.amazonaws
From af730be04a435a588c7e90d2110dfb2d605357c1 Mon Sep 17 00:00:00 2001
From: Davide Melfi
Date: Thu, 19 Mar 2026 17:54:59 +0000
Subject: [PATCH 41/49] Revert upgrade in Jackson serialization (#593)
* chore: reverted the poms
* chore: upgrading the poms
* chore: update docs
* chore: add targets to tests
* chore: add workflow_dispatch
* chore: add comment to trigger testing
* chore: downgrade jackson library
* chore: removing comment
* chore: new version
* chore: remove file
---------
Co-authored-by: Davide Melfi
---
.../RELEASE.CHANGELOG.md | 5 +
aws-lambda-java-serialization/pom.xml | 4 +-
.../events/LambdaEventSerializers.java | 524 +++++++++---------
3 files changed, 281 insertions(+), 252 deletions(-)
diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
index 59ed3c8cb..4a0e8ca2a 100644
--- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
@@ -1,3 +1,8 @@
+### March 19, 2026
+`1.3.1`:
+- Revert `jackson-databind` dependency from 2.18.6 to 2.15.4
+- Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy`
+
### March 11, 2026
`1.3.0`:
- Update `jackson-databind` dependency from 2.15.4 to 2.18.6
diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml
index 93c27d879..a71eb1f8d 100644
--- a/aws-lambda-java-serialization/pom.xml
+++ b/aws-lambda-java-serialization/pom.xml
@@ -4,7 +4,7 @@
com.amazonaws
aws-lambda-java-serialization
- 1.3.0
+ 1.3.1
jar
AWS Lambda Java Runtime Serialization
@@ -32,7 +32,7 @@
1.8
1.8
com.amazonaws.lambda.thirdparty
- 2.18.6
+ 2.15.4
2.10.1
20231013
7.3.2
diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
index 89401a91e..3f01d99b1 100644
--- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
+++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
@@ -19,7 +19,6 @@
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil;
import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule;
import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule;
@@ -40,279 +39,304 @@
*
* Option 1 (Preferred):
* 1. Add Class name to SUPPORTED_EVENTS
- * 2. Add Mixin Class to com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if needed)
+ * 2. Add Mixin Class to
+ * com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if
+ * needed)
* 3. Add entries to MIXIN_MAP for event class and sub classes (if needed)
- * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if needed)
- * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of a mixin)
+ * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if
+ * needed)
+ * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of
+ * a mixin)
*
* Option 2 (longer - for event models that do not work with Jackson or GSON):
* 1. Add Class name to SUPPORTED_EVENTS
- * 2. Add serializer (using org.json) to com.amazonaws.services.lambda.runtime.serialization.events.serializers
+ * 2. Add serializer (using org.json) to
+ * com.amazonaws.services.lambda.runtime.serialization.events.serializers
* 3. Add class name and serializer to SERIALIZER_MAP
*/
public class LambdaEventSerializers {
- /**
- * list of supported events
- */
- private static final List SUPPORTED_EVENTS = Stream.of(
- "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent",
- "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent",
- "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
- "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
- "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
- "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
- "com.amazonaws.services.lambda.runtime.events.CognitoEvent",
- "com.amazonaws.services.lambda.runtime.events.ConfigEvent",
- "com.amazonaws.services.lambda.runtime.events.ConnectEvent",
- "com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
- "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
- "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent",
- "com.amazonaws.services.lambda.runtime.events.KinesisEvent",
- "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
- "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent",
- "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent",
- "com.amazonaws.services.lambda.runtime.events.LexEvent",
- "com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
- "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
- "com.amazonaws.services.s3.event.S3EventNotification",
- "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification",
- "com.amazonaws.services.lambda.runtime.events.S3Event",
- "com.amazonaws.services.lambda.runtime.events.SNSEvent",
- "com.amazonaws.services.lambda.runtime.events.SQSEvent")
- .collect(Collectors.toList());
+ /**
+ * list of supported events
+ */
+ private static final List SUPPORTED_EVENTS = Stream.of(
+ "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent",
+ "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent",
+ "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
+ "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
+ "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
+ "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
+ "com.amazonaws.services.lambda.runtime.events.CognitoEvent",
+ "com.amazonaws.services.lambda.runtime.events.ConfigEvent",
+ "com.amazonaws.services.lambda.runtime.events.ConnectEvent",
+ "com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
+ "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
+ "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent",
+ "com.amazonaws.services.lambda.runtime.events.KinesisEvent",
+ "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
+ "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent",
+ "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent",
+ "com.amazonaws.services.lambda.runtime.events.LexEvent",
+ "com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
+ "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
+ "com.amazonaws.services.s3.event.S3EventNotification",
+ "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification",
+ "com.amazonaws.services.lambda.runtime.events.S3Event",
+ "com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ "com.amazonaws.services.lambda.runtime.events.SQSEvent")
+ .collect(Collectors.toList());
- /**
- * list of events incompatible with Jackson, with serializers explicitly defined
- * Classes are incompatible with Jackson for any of the following reasons:
- * 1. different constructor/setter types from getter types
- * 2. various bugs within Jackson
- */
- private static final Map SERIALIZER_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>()))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * list of events incompatible with Jackson, with serializers explicitly defined
+ * Classes are incompatible with Jackson for any of the following reasons:
+ * 1. different constructor/setter types from getter types
+ * 2. various bugs within Jackson
+ */
+ private static final Map SERIALIZER_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification",
+ new S3EventSerializer<>()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification",
+ new S3EventSerializer<>()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event",
+ new S3EventSerializer<>()))
+ .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
- /**
- * Maps supported event classes to mixin classes with Jackson annotations.
- * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a
- * json field that does not match the Jave field name, then a Mixin is required.
- */
- @SuppressWarnings("rawtypes")
- private static final Map MIXIN_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
- CloudFormationCustomResourceEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
- CloudFrontEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
- CloudWatchLogsEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
- CodeCommitEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record",
- CodeCommitEventMixin.RecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
- ConnectEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details",
- ConnectEventMixin.DetailsMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData",
- ConnectEventMixin.ContactDataMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint",
- ConnectEventMixin.CustomerEndpointMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint",
- ConnectEventMixin.SystemEndpointMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
- DynamodbEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
- DynamodbEventMixin.DynamodbStreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord",
- DynamodbEventMixin.StreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- DynamodbEventMixin.StreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue",
- DynamodbEventMixin.AttributeValueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- DynamodbEventMixin.AttributeValueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
- DynamodbTimeWindowEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
- KinesisEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record",
- KinesisEventMixin.RecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
- KinesisTimeWindowEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
- ScheduledEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
- SecretsManagerRotationEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- SNSEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord",
- SNSEventMixin.SNSRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
- SQSEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage",
- SQSEventMixin.SQSMessageMixin.class))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * Maps supported event classes to mixin classes with Jackson annotations.
+ * Jackson annotations are not loaded through the ClassLoader so if a Java field
+ * is serialized or deserialized from a
+ * json field that does not match the Jave field name, then a Mixin is required.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map MIXIN_MAP = Stream.of(
+ new SimpleEntry<>(
+ "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
+ CloudFormationCustomResourceEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
+ CloudFrontEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
+ CloudWatchLogsEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
+ CodeCommitEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record",
+ CodeCommitEventMixin.RecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
+ ConnectEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details",
+ ConnectEventMixin.DetailsMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData",
+ ConnectEventMixin.ContactDataMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint",
+ ConnectEventMixin.CustomerEndpointMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
+ ConnectEventMixin.QueueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint",
+ ConnectEventMixin.SystemEndpointMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
+ DynamodbEventMixin.class),
+ new SimpleEntry<>(
+ "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
+ DynamodbEventMixin.DynamodbStreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord",
+ DynamodbEventMixin.StreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ DynamodbEventMixin.StreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue",
+ DynamodbEventMixin.AttributeValueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ DynamodbEventMixin.AttributeValueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
+ DynamodbTimeWindowEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
+ KinesisEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record",
+ KinesisEventMixin.RecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
+ KinesisTimeWindowEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
+ ScheduledEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
+ SecretsManagerRotationEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ SNSEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord",
+ SNSEventMixin.SNSRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
+ SQSEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage",
+ SQSEventMixin.SQSMessageMixin.class))
+ .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
- /**
- * If mixins are required for inner classes of an event, then those nested classes must be specified here.
- */
- @SuppressWarnings("rawtypes")
- private static final Map> NESTED_CLASS_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage"))))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * If mixins are required for inner classes of an event, then those nested
+ * classes must be specified here.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map> NESTED_CLASS_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
+ new SimpleEntry<>(
+ "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage"))))
+ .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
- /**
- * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter
- * method setSns, for some magical reasons, using both mixins and a naming strategy works
- */
- private static final Map NAMING_STRATEGY_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- new PropertyNamingStrategies.UpperCamelCaseStrategy()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
- new PropertyNamingStrategies.UpperCamelCaseStrategy())
- )
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * If event requires a naming strategy. For example, when someone names the
+ * getter method getSNS and the setter
+ * method setSns, for some magical reasons, using both mixins and a naming
+ * strategy works
+ */
+ private static final Map NAMING_STRATEGY_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ new PropertyNamingStrategy.PascalCaseStrategy()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
+ new PropertyNamingStrategy.PascalCaseStrategy()))
+ .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
- /**
- * Returns whether the class name is a Lambda supported event model.
- * @param className class name as string
- * @return whether the event model is supported
- */
- public static boolean isLambdaSupportedEvent(String className) {
- return SUPPORTED_EVENTS.contains(className);
- }
-
- /**
- * Return a serializer for the event class
- * @return a specific PojoSerializer or modified JacksonFactory instance with mixins and modules added in
- */
- @SuppressWarnings({"unchecked"})
- public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) {
- // if serializer specifically defined for event then use that
- if (SERIALIZER_MAP.containsKey(eventClass.getName())) {
- return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass).withClassLoader(classLoader);
- }
- // else use a Jackson ObjectMapper instance
- JacksonFactory factory = JacksonFactory.getInstance();
- // if mixins required for class, then apply
- if (MIXIN_MAP.containsKey(eventClass.getName())) {
- factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName()));
+ /**
+ * Returns whether the class name is a Lambda supported event model.
+ *
+ * @param className class name as string
+ * @return whether the event model is supported
+ */
+ public static boolean isLambdaSupportedEvent(String className) {
+ return SUPPORTED_EVENTS.contains(className);
}
- // if event model has nested classes then load those classes and check if mixins apply
- if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) {
- List extends NestedClass> nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName());
- for (NestedClass nestedClass: nestedClasses) {
- // if mixin exists for nested class then apply
- if (MIXIN_MAP.containsKey(nestedClass.className)) {
- factory = tryLoadingNestedClass(classLoader, factory, nestedClass);
+
+ /**
+ * Return a serializer for the event class
+ *
+ * @return a specific PojoSerializer or modified JacksonFactory instance with
+ * mixins and modules added in
+ */
+ @SuppressWarnings({ "unchecked" })
+ public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) {
+ // if serializer specifically defined for event then use that
+ if (SERIALIZER_MAP.containsKey(eventClass.getName())) {
+ return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass)
+ .withClassLoader(classLoader);
}
- }
- }
- // load DateModules
- factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader));
- // load naming strategy if needed
- if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) {
- factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName()));
+ // else use a Jackson ObjectMapper instance
+ JacksonFactory factory = JacksonFactory.getInstance();
+ // if mixins required for class, then apply
+ if (MIXIN_MAP.containsKey(eventClass.getName())) {
+ factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName()));
+ }
+ // if event model has nested classes then load those classes and check if mixins
+ // apply
+ if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) {
+ List extends NestedClass> nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName());
+ for (NestedClass nestedClass : nestedClasses) {
+ // if mixin exists for nested class then apply
+ if (MIXIN_MAP.containsKey(nestedClass.className)) {
+ factory = tryLoadingNestedClass(classLoader, factory, nestedClass);
+ }
+ }
+ }
+ // load DateModules
+ factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader));
+ // load naming strategy if needed
+ if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) {
+ factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName()));
+ }
+ return factory.getSerializer(eventClass);
}
- return factory.getSerializer(eventClass);
- }
- /**
- * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP} into the {@link JacksonFactory} object.
- * Will allow initial failure for {@link AlternateNestedClass} objects and try again with their alternate class name
- * @return a modified JacksonFactory instance with mixins added in
- */
- private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory, NestedClass nestedClass) {
- Class> eventClazz;
- Class> mixinClazz;
- try {
- eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader);
- mixinClazz = MIXIN_MAP.get(nestedClass.getClassName());
- } catch (ReflectUtil.ReflectException e) {
- if (nestedClass instanceof AlternateNestedClass) {
- AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass;
- eventClazz = SerializeUtil.loadCustomerClass(alternateNestedClass.getAlternateClassName(), classLoader);
- mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName());
- } else {
- throw e;
- }
- }
+ /**
+ * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP}
+ * into the {@link JacksonFactory} object.
+ * Will allow initial failure for {@link AlternateNestedClass} objects and try
+ * again with their alternate class name
+ *
+ * @return a modified JacksonFactory instance with mixins added in
+ */
+ private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory,
+ NestedClass nestedClass) {
+ Class> eventClazz;
+ Class> mixinClazz;
+ try {
+ eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader);
+ mixinClazz = MIXIN_MAP.get(nestedClass.getClassName());
+ } catch (ReflectUtil.ReflectException e) {
+ if (nestedClass instanceof AlternateNestedClass) {
+ AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass;
+ eventClazz = SerializeUtil.loadCustomerClass(
+ alternateNestedClass.getAlternateClassName(), classLoader);
+ mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName());
+ } else {
+ throw e;
+ }
+ }
- return factory.withMixin(eventClazz, mixinClazz);
- }
+ return factory.withMixin(eventClazz, mixinClazz);
+ }
- private static class NestedClass {
- private final String className;
+ private static class NestedClass {
+ private final String className;
- protected NestedClass(String className) {
- this.className = className;
- }
+ protected NestedClass(String className) {
+ this.className = className;
+ }
- protected String getClassName() {
- return className;
+ protected String getClassName() {
+ return className;
+ }
}
- }
- private static class AlternateNestedClass extends NestedClass {
- private final String alternateClassName;
+ private static class AlternateNestedClass extends NestedClass {
+ private final String alternateClassName;
- private AlternateNestedClass(String className, String alternateClassName) {
- super(className);
- this.alternateClassName = alternateClassName;
- }
+ private AlternateNestedClass(String className, String alternateClassName) {
+ super(className);
+ this.alternateClassName = alternateClassName;
+ }
- private String getAlternateClassName() {
- return alternateClassName;
+ private String getAlternateClassName() {
+ return alternateClassName;
+ }
}
- }
}
From c201b4dfcfe2fc663671b6fe27617d0f57d1313e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 23 Mar 2026 15:38:28 +0000
Subject: [PATCH 42/49] Bump actions/checkout from 5 to 6 (#574)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Davide Melfi
---
.github/workflows/aws-lambda-java-core.yml | 2 +-
.github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +-
.github/workflows/aws-lambda-java-events.yml | 2 +-
.github/workflows/aws-lambda-java-log4j2.yml | 2 +-
.github/workflows/aws-lambda-java-profiler.yml | 2 +-
.github/workflows/aws-lambda-java-serialization.yml | 2 +-
.github/workflows/aws-lambda-java-tests.yml | 2 +-
.github/workflows/repo-sync.yml | 2 +-
.github/workflows/runtime-interface-client_merge_to_main.yml | 2 +-
.github/workflows/runtime-interface-client_pr.yml | 4 ++--
.github/workflows/samples.yml | 4 ++--
11 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml
index b1bed919f..270beef8d 100644
--- a/.github/workflows/aws-lambda-java-core.yml
+++ b/.github/workflows/aws-lambda-java-core.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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 1f1f08870..d36bdfd13 100644
--- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
+++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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 2d101018d..fc4576a9b 100644
--- a/.github/workflows/aws-lambda-java-events.yml
+++ b/.github/workflows/aws-lambda-java-events.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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 e9f6a56c1..051498ca7 100644
--- a/.github/workflows/aws-lambda-java-log4j2.yml
+++ b/.github/workflows/aws-lambda-java-log4j2.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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
index a3afe3729..472c3b57d 100644
--- a/.github/workflows/aws-lambda-java-profiler.yml
+++ b/.github/workflows/aws-lambda-java-profiler.yml
@@ -22,7 +22,7 @@ jobs:
contents: read
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v4
diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml
index 13b7e08b0..fd55b22f2 100644
--- a/.github/workflows/aws-lambda-java-serialization.yml
+++ b/.github/workflows/aws-lambda-java-serialization.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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 363647cc4..bea7ff7d8 100644
--- a/.github/workflows/aws-lambda-java-tests.yml
+++ b/.github/workflows/aws-lambda-java-tests.yml
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- 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 2d97bc868..6a918fde3 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -20,7 +20,7 @@ jobs:
env:
IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
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 3560207f3..73dd61f89 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@v5
+ - uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v4
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index dcad4fa0a..f243afcc6 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -18,7 +18,7 @@ jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v4
@@ -43,7 +43,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v4
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index aebb708a7..f8a2f1dd7 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -21,7 +21,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
@@ -45,7 +45,7 @@ jobs:
custom-serialization:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
# Set up both Java 8 and 21
- name: Set up Java 8 and 21
uses: actions/setup-java@v4
From d8ffbf1c5e13280b1503bc487816eb33827c0766 Mon Sep 17 00:00:00 2001
From: Davide Melfi
Date: Mon, 23 Mar 2026 22:03:09 +0000
Subject: [PATCH 43/49] Github Actions Improvements (#594)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* ci: ๐ Github Actions improvements
* ci: dependent library test fan out
* ci: add workflow_dispatch
* build: upgrading junit-jupiter everywhere
---------
Co-authored-by: Davide Melfi
---
.github/workflows/aws-lambda-java-core.yml | 18 ++++--------------
.../aws-lambda-java-events-sdk-transformer.yml | 12 ++++++++----
.github/workflows/aws-lambda-java-events.yml | 14 ++++----------
.github/workflows/aws-lambda-java-log4j2.yml | 13 ++++++++-----
.github/workflows/aws-lambda-java-profiler.yml | 1 +
.../aws-lambda-java-serialization.yml | 12 ++++++++----
.github/workflows/aws-lambda-java-tests.yml | 14 ++++++--------
.../runtime-interface-client_merge_to_main.yml | 3 ++-
.../workflows/runtime-interface-client_pr.yml | 16 +++++++++++-----
.github/workflows/samples.yml | 17 ++++++++++++-----
.gitignore | 2 +-
aws-lambda-java-events-sdk-transformer/pom.xml | 3 ++-
aws-lambda-java-events/pom.xml | 3 ++-
.../pom.xml | 2 +-
aws-lambda-java-tests/pom.xml | 2 +-
.../examples/cdk/pom.xml | 2 +-
samples/kinesis-firehose-event-handler/pom.xml | 3 ++-
17 files changed, 74 insertions(+), 63 deletions(-)
diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml
index 270beef8d..373acf757 100644
--- a/.github/workflows/aws-lambda-java-core.yml
+++ b/.github/workflows/aws-lambda-java-core.yml
@@ -1,9 +1,10 @@
# This workflow will be triggered if there will be changes to aws-lambda-java-core
-# package and it builds the package and the packages that depend on it.
+# package and it builds the package.
name: Java CI aws-lambda-java-core
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
@@ -29,18 +30,7 @@ jobs:
with:
java-version: 8
distribution: corretto
-
- # Install base module
+ cache: maven
+
- name: Install core with Maven
run: mvn -B install --file aws-lambda-java-core/pom.xml
-
- # Package modules that depend on base module
- - name: Package log4j2 with Maven
- run: mvn -B package --file aws-lambda-java-log4j2/pom.xml
-
- # Test Runtime Interface Client
- - name: Run 'pr' target
- working-directory: ./aws-lambda-java-runtime-interface-client
- run: make pr
- env:
- IS_JAVA_8: true
diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
index d36bdfd13..713f97211 100644
--- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
+++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
@@ -1,17 +1,21 @@
-# This workflow will be triggered if there will be changes to
-# aws-lambda-java-events-sdk-transformer package and it builds the package.
+# This workflow will be triggered if there will be changes to
+# aws-lambda-java-events-sdk-transformer package or its dependency (events),
+# and it builds the package.
name: Java CI aws-lambda-java-events-sdk-transformer
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
- 'aws-lambda-java-events-sdk-transformer/**'
+ - 'aws-lambda-java-events/**'
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-events-sdk-transformer/**'
+ - 'aws-lambda-java-events/**'
- '.github/workflows/aws-lambda-java-events-sdk-transformer.yml'
permissions:
@@ -29,11 +33,11 @@ jobs:
with:
java-version: 8
distribution: corretto
+ cache: maven
- # Install base module
+ # Install dependency
- name: Install events with Maven
run: mvn -B install --file aws-lambda-java-events/pom.xml
# Package target module
- name: Package events-sdk-transformer with Maven
run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml
-
diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml
index fc4576a9b..0a8725339 100644
--- a/.github/workflows/aws-lambda-java-events.yml
+++ b/.github/workflows/aws-lambda-java-events.yml
@@ -1,9 +1,10 @@
# This workflow will be triggered if there will be changes to aws-lambda-java-events
-# package and it builds the package and the packages that depend on it.
+# package and it builds the package.
name: Java CI aws-lambda-java-events
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
@@ -29,14 +30,7 @@ jobs:
with:
java-version: 8
distribution: corretto
-
- # Install base module
+ cache: maven
+
- name: Install events with Maven
run: mvn -B install --file aws-lambda-java-events/pom.xml
-
- # Package modules that depend on base module
- - name: Package serialization with Maven
- run: mvn -B package --file aws-lambda-java-serialization/pom.xml
- - name: Package events-sdk-transformer with Maven
- run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml
-
diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml
index 051498ca7..315678339 100644
--- a/.github/workflows/aws-lambda-java-log4j2.yml
+++ b/.github/workflows/aws-lambda-java-log4j2.yml
@@ -1,17 +1,20 @@
-# This workflow will be triggered if there will be changes to
-# aws-lambda-java-log4j2 package and it builds the package.
+# This workflow will be triggered if there will be changes to
+# aws-lambda-java-log4j2 package or its dependency (core), and it builds the package.
name: Java CI aws-lambda-java-log4j2
on:
push:
+ workflow_dispatch:
branches: [ main ]
paths:
- 'aws-lambda-java-log4j2/**'
+ - 'aws-lambda-java-core/**'
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-log4j2/**'
+ - 'aws-lambda-java-core/**'
- '.github/workflows/aws-lambda-java-log4j2.yml'
permissions:
@@ -29,11 +32,11 @@ jobs:
with:
java-version: 8
distribution: corretto
-
- # Install base module
+ cache: maven
+
+ # Install dependency
- name: Install core with Maven
run: mvn -B install --file aws-lambda-java-core/pom.xml
# Package target module
- name: Package log4j2 with Maven
run: mvn -B package --file aws-lambda-java-log4j2/pom.xml
-
diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml
index 472c3b57d..1007e7124 100644
--- a/.github/workflows/aws-lambda-java-profiler.yml
+++ b/.github/workflows/aws-lambda-java-profiler.yml
@@ -29,6 +29,7 @@ jobs:
with:
java-version: 21
distribution: corretto
+ cache: maven
- name: Issue AWS credentials
uses: aws-actions/configure-aws-credentials@v4
diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml
index fd55b22f2..78d23c37b 100644
--- a/.github/workflows/aws-lambda-java-serialization.yml
+++ b/.github/workflows/aws-lambda-java-serialization.yml
@@ -1,17 +1,20 @@
-# This workflow will be triggered if there will be changes to aws-lambda-java-serialization
-# package and it builds the package and the packages that depend on it.
+# This workflow will be triggered if there will be changes to aws-lambda-java-serialization
+# package or its dependency (events), and it builds the package.
name: Java CI aws-lambda-java-serialization
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
- 'aws-lambda-java-serialization/**'
+ - 'aws-lambda-java-events/**'
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-serialization/**'
+ - 'aws-lambda-java-events/**'
- '.github/workflows/aws-lambda-java-serialization.yml'
permissions:
@@ -29,8 +32,9 @@ jobs:
with:
java-version: 8
distribution: corretto
-
- # Install base module
+ cache: maven
+
+ # Install dependency
- name: Install events with Maven
run: mvn -B install --file aws-lambda-java-events/pom.xml
diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml
index bea7ff7d8..a52181dff 100644
--- a/.github/workflows/aws-lambda-java-tests.yml
+++ b/.github/workflows/aws-lambda-java-tests.yml
@@ -1,5 +1,5 @@
# This workflow will be triggered if there will be changes to aws-lambda-java-tests
-# package and it builds the package and the packages that depend on it.
+# package or its dependencies (events, serialization), and it builds the package.
name: Java CI aws-lambda-java-tests
@@ -9,16 +9,14 @@ on:
branches: [ main ]
paths:
- 'aws-lambda-java-tests/**'
- - 'aws-lambda-java-runtime-interface-client/**'
- - 'aws-lambda-java-serialization/**'
- 'aws-lambda-java-events/**'
+ - 'aws-lambda-java-serialization/**'
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-tests/**'
- - 'aws-lambda-java-runtime-interface-client/**'
- - 'aws-lambda-java-serialization/**'
- 'aws-lambda-java-events/**'
+ - 'aws-lambda-java-serialization/**'
- '.github/workflows/aws-lambda-java-tests.yml'
permissions:
@@ -36,8 +34,9 @@ jobs:
with:
java-version: 8
distribution: corretto
-
- # Install base module
+ cache: maven
+
+ # Install dependencies
- name: Install events with Maven
run: mvn -B install --file aws-lambda-java-events/pom.xml
- name: Install serialization with Maven
@@ -46,4 +45,3 @@ jobs:
# Package target module
- name: Package tests with Maven
run: mvn -B package --file aws-lambda-java-tests/pom.xml
-
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index 73dd61f89..da2389d73 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -11,12 +11,12 @@
name: Publish artifact for aws-lambda-java-runtime-interface-client
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
- 'aws-lambda-java-runtime-interface-client/**'
- '.github/workflows/runtime-interface-client_*.yml'
- workflow_dispatch:
jobs:
@@ -35,6 +35,7 @@ jobs:
with:
java-version: 8
distribution: corretto
+ cache: maven
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index f243afcc6..59b1c8aed 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -1,13 +1,17 @@
-# This workflow will be triggered if there will be changes to
-# aws-lambda-java-runtime-interface-client package and it builds the package.
+# This workflow will be triggered if there will be changes to
+# aws-lambda-java-runtime-interface-client package or its dependencies (core, serialization),
+# and it builds the package.
name: PR to runtime-interface-client
on:
+ workflow_dispatch:
pull_request:
branches: [ '*' ]
paths:
- 'aws-lambda-java-runtime-interface-client/**'
+ - 'aws-lambda-java-core/**'
+ - 'aws-lambda-java-serialization/**'
- '.github/workflows/runtime-interface-client_*.yml'
permissions:
@@ -25,7 +29,8 @@ jobs:
with:
java-version: 8
distribution: corretto
-
+ cache: maven
+
- name: Build and install core dependency locally
working-directory: ./aws-lambda-java-core
run: mvn clean install
@@ -39,7 +44,7 @@ jobs:
run: make pr
env:
IS_JAVA_8: true
-
+
build:
runs-on: ubuntu-latest
steps:
@@ -50,6 +55,7 @@ jobs:
with:
java-version: 8
distribution: corretto
+ cache: maven
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -61,7 +67,7 @@ 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
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index f8a2f1dd7..a76c3fb25 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -1,17 +1,24 @@
-# This workflow will be triggered if there will be changes to aws-lambda-java-core
-# package and it builds the package and the packages that depend on it.
+# This workflow will be triggered if there will be changes to samples
+# or their dependencies (events, serialization, tests).
name: Java CI samples
on:
+ workflow_dispatch:
push:
branches: [ main ]
paths:
- 'samples/**'
+ - 'aws-lambda-java-events/**'
+ - 'aws-lambda-java-serialization/**'
+ - 'aws-lambda-java-tests/**'
pull_request:
branches: [ '*' ]
paths:
- 'samples/**'
+ - 'aws-lambda-java-events/**'
+ - 'aws-lambda-java-serialization/**'
+ - 'aws-lambda-java-tests/**'
- '.github/workflows/samples.yml'
permissions:
@@ -27,14 +34,13 @@ jobs:
with:
java-version: 8
distribution: corretto
+ cache: maven
- # Install events module
+ # Install dependencies
- 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
@@ -54,6 +60,7 @@ jobs:
8
21
distribution: corretto
+ cache: maven
# Install events module using Java 8
- name: Install events with Maven
diff --git a/.gitignore b/.gitignore
index 5eb5456a1..b5d289a8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,4 +37,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin
/scratch/
.vscode
.kiro
-build
\ No newline at end of file
+build
diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml
index d719ec8ac..7c72e98c4 100644
--- a/aws-lambda-java-events-sdk-transformer/pom.xml
+++ b/aws-lambda-java-events-sdk-transformer/pom.xml
@@ -38,6 +38,7 @@
1.8
1.11.914
2.15.40
+ 5.14.3
@@ -70,7 +71,7 @@
org.junit.jupiter
junit-jupiter-engine
- 5.7.0
+ ${junit-jupiter.version}
test
diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml
index 714c825d9..ba93601dc 100644
--- a/aws-lambda-java-events/pom.xml
+++ b/aws-lambda-java-events/pom.xml
@@ -39,6 +39,7 @@
UTF-8
2.20.1
2.40.1
+ 5.14.3
@@ -58,7 +59,7 @@
org.junit.jupiter
junit-jupiter-engine
- 5.9.2
+ ${junit-jupiter.version}
test
diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml
index 65304c61a..f84ffe558 100644
--- a/aws-lambda-java-runtime-interface-client/pom.xml
+++ b/aws-lambda-java-runtime-interface-client/pom.xml
@@ -37,7 +37,7 @@
0.8.12
2.4
3.1.1
- 5.9.2
+ 5.14.3
3.4.0
+ 5.9.2
0.8.7
@@ -250,6 +255,9 @@
org.apache.maven.plugins
maven-surefire-plugin
2.22.2
+
+ true
+
diff --git a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml
index d84b2bd1e..4b46f4e2b 100644
--- a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml
+++ b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml
@@ -11,7 +11,7 @@
UTF-8
2.155.0
[10.0.0,11.0.0)
- 5.14.3
+ 5.12.2
diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml
index 3bb0051ba..0db8ed83a 100644
--- a/samples/kinesis-firehose-event-handler/pom.xml
+++ b/samples/kinesis-firehose-event-handler/pom.xml
@@ -35,7 +35,9 @@
1.8
1.8
UTF-8
- 5.14.3
+ 5.12.2
+ 3.5.4
+
@@ -69,7 +71,10 @@
org.apache.maven.plugins
maven-surefire-plugin
- 2.22.2
+ ${maven-surefire-plugin.version}
+
+ true
+
From 3e23521fbbae085a21610d74055cc3b8b77c2026 Mon Sep 17 00:00:00 2001
From: Davide Melfi
Date: Mon, 30 Mar 2026 15:43:44 +0100
Subject: [PATCH 45/49] Add serialization round-trip tests for event types
(#598)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* test: ๐งช add roundtrip serialization test utility
* test: fix false positives epoch format errors, added comment about this
in the serialization package.
* test: fixed false positives DateTime differences
* test: fixing error in lex event fixture
* test: fixing connect event
* test: fixing api gateway proxy event false negative
* test: fixing CloudFront and S3 event false negatives
* build: adding mise to .gitignore
* test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures
* test: Add round-trip fixtures for 4 registered events
* test: Add round-trip tests for 11 response event types
* test: including IAM Policy Response roundtrip test
* test: add test for JsonNodeUtils
* docs: add tests 1.1.2 changelog entry
---------
Co-authored-by: Davide Melfi
---
.gitignore | 1 +
aws-lambda-java-serialization/mise.toml | 2 -
.../events/modules/DateModule.java | 15 +-
.../lambda/runtime/tests/JsonNodeUtils.java | 110 +++++++++++++
.../runtime/tests/LambdaEventAssert.java | 147 +++++++++++++++++
.../lambda/runtime/tests/EventLoaderTest.java | 15 ++
.../runtime/tests/JsonNodeUtilsTest.java | 155 ++++++++++++++++++
.../runtime/tests/LambdaEventAssertTest.java | 71 ++++++++
...sponseEventSerializationRoundTripTest.java | 59 +++++++
.../tests/SerializationRoundTripTest.java | 108 ++++++++++++
...steredEventSerializationRoundTripTest.java | 90 ++++++++++
.../src/test/resources/apigw_http_event.json | 12 --
.../src/test/resources/apigw_rest_event.json | 25 +--
.../test/resources/apigw_websocket_event.json | 88 ++++++++++
.../resources/appsync_authorizer_event.json | 15 ++
.../appsync_authorizer_response.json | 11 ++
.../src/test/resources/cloudfront_event.json | 1 -
...ognito_userpool_create_auth_challenge.json | 37 +++++
.../cognito_userpool_custom_message.json | 28 ++++
...ognito_userpool_define_auth_challenge.json | 32 ++++
.../cognito_userpool_migrate_user.json | 35 ++++
.../cognito_userpool_postauthentication.json | 20 +++
.../cognito_userpool_postconfirmation.json | 20 +++
...cognito_userpool_pre_token_generation.json | 48 ++++++
.../cognito_userpool_preauthentication.json | 20 +++
.../cognito/cognito_userpool_presignup.json | 27 +++
...ognito_userpool_verify_auth_challenge.json | 27 +++
.../test/resources/cognito_sync_event.json | 20 +++
.../src/test/resources/connect_event.json | 9 -
.../resources/ddb/dynamo_event_roundtrip.json | 97 +++++++++++
.../ddb/dynamo_time_window_event.json | 75 +++++++++
.../src/test/resources/iot_button_event.json | 5 +
.../test/resources/kafka_event_roundtrip.json | 22 +++
...nalytics_firehose_input_preprocessing.json | 14 ++
...nalytics_input_preprocessing_response.json | 9 +
.../kinesis_analytics_output_delivery.json | 13 ++
...is_analytics_output_delivery_response.json | 8 +
...analytics_streams_input_preprocessing.json | 17 ++
.../kinesis/kinesis_event_roundtrip.json | 21 +++
.../kinesis/kinesis_time_window_event.json | 32 ++++
.../test/resources/lex_event_roundtrip.json | 24 +++
.../src/test/resources/mq_event.json | 24 +--
.../test/resources/msk_firehose_event.json | 2 +-
.../msk_firehose_event_roundtrip.json | 18 ++
.../src/test/resources/partial_pojo.json | 4 +
.../resources/rabbitmq_event_roundtrip.json | 51 ++++++
.../test/resources/response/alb_response.json | 15 ++
.../response/apigw_proxy_response.json | 15 ++
.../response/apigw_v2_http_response.json | 16 ++
.../response/apigw_v2_websocket_response.json | 14 ++
.../response/msk_firehose_response.json | 9 +
.../resources/response/s3_batch_response.json | 12 ++
.../response/simple_iam_policy_response.json | 7 +
.../response/sqs_batch_response.json | 7 +
.../src/test/resources/s3_batch_event.json | 15 ++
.../src/test/resources/s3_event.json | 2 +
.../resources/s3_object_lambda_event.json | 29 ++++
.../resources/time_window_event_response.json | 10 ++
.../src/test/resources/unstable_pojo.json | 3 +
59 files changed, 1783 insertions(+), 55 deletions(-)
delete mode 100644 aws-lambda-java-serialization/mise.toml
create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java
create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java
create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java
create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java
create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java
create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java
create mode 100644 aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json
create mode 100644 aws-lambda-java-tests/src/test/resources/cognito_sync_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/iot_button_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/partial_pojo.json
create mode 100644 aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/alb_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/s3_batch_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json
create mode 100644 aws-lambda-java-tests/src/test/resources/time_window_event_response.json
create mode 100644 aws-lambda-java-tests/src/test/resources/unstable_pojo.json
diff --git a/.gitignore b/.gitignore
index b5d289a8b..5a277e5d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin
.vscode
.kiro
build
+mise.toml
diff --git a/aws-lambda-java-serialization/mise.toml b/aws-lambda-java-serialization/mise.toml
deleted file mode 100644
index 854b93b56..000000000
--- a/aws-lambda-java-serialization/mise.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[tools]
-java = "corretto-8"
diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java
index 8a6954e34..acc8bde2a 100644
--- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java
+++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java
@@ -15,10 +15,17 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
/**
- * The AWS API represents a date as a double, which specifies the fractional
- * number of seconds since the epoch. Java's Date, however, represents a date as
- * a long, which specifies the number of milliseconds since the epoch. This
- * class is used to translate between these two formats.
+ * The AWS API represents a date as a double (fractional seconds since epoch).
+ * Java's Date uses a long (milliseconds since epoch). This module translates
+ * between the two formats.
+ *
+ *
+ * Round-trip caveats: The serializer always writes via
+ * {@link JsonGenerator#writeNumber(double)}, so integer epochs
+ * (e.g. {@code 1428537600}) round-trip as decimal ({@code 1.4285376E9}).
+ * Sub-millisecond precision is lost because {@link java.util.Date}
+ * has milliseconds precision.
+ *
*
* This class is copied from LambdaEventBridgeservice
* com.amazon.aws.lambda.stream.ddb.DateModule
diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java
new file mode 100644
index 000000000..f9f4e1eb6
--- /dev/null
+++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java
@@ -0,0 +1,110 @@
+package com.amazonaws.services.lambda.runtime.tests;
+
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode;
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import org.joda.time.DateTime;
+
+/**
+ * Utility methods for working with shaded Jackson {@link JsonNode} trees.
+ *
+ *
+ * Package-private โ not part of the public API.
+ *
+ */
+class JsonNodeUtils {
+
+ private static final Pattern ISO_DATE_REGEX = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T.+");
+
+ private JsonNodeUtils() {
+ }
+
+ /**
+ * Recursively removes all fields whose value is {@code null} from the
+ * tree. This mirrors the serializer's {@code Include.NON_NULL} behaviour
+ * so that explicit nulls in the fixture don't cause false-positive diffs.
+ */
+ static JsonNode stripNulls(JsonNode node) {
+ if (node.isObject()) {
+ ObjectNode obj = (ObjectNode) node;
+ Iterator fieldNames = obj.fieldNames();
+ while (fieldNames.hasNext()) {
+ String field = fieldNames.next();
+ if (obj.get(field).isNull()) {
+ fieldNames.remove();
+ } else {
+ stripNulls(obj.get(field));
+ }
+ }
+ } else if (node.isArray()) {
+ for (JsonNode element : node) {
+ stripNulls(element);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Recursively walks both trees and collects human-readable diff lines.
+ */
+ static void diffNodes(String path, JsonNode expected, JsonNode actual, List diffs) {
+ if (expected.equals(actual))
+ return;
+
+ // Compares two datetime strings by parsed instant, because DateTimeModule
+ // normalizes the format on serialization (e.g. "+0000" โ "Z", "Z" โ ".000Z")
+ if (areSameDateTime(expected.textValue(), actual.textValue())) {
+ return;
+ }
+
+ if (expected.isObject() && actual.isObject()) {
+ TreeSet allKeys = new TreeSet<>();
+ expected.fieldNames().forEachRemaining(allKeys::add);
+ actual.fieldNames().forEachRemaining(allKeys::add);
+ for (String key : allKeys) {
+ diffChild(path + "." + key, expected.get(key), actual.get(key), diffs);
+ }
+ } else if (expected.isArray() && actual.isArray()) {
+ for (int i = 0; i < Math.max(expected.size(), actual.size()); i++) {
+ diffChild(path + "[" + i + "]", expected.get(i), actual.get(i), diffs);
+ }
+ } else {
+ diffs.add("CHANGED " + path + " : " + summarize(expected) + " -> " + summarize(actual));
+ }
+ }
+
+ /**
+ * Compares two strings by parsed instant when both look like ISO-8601 dates,
+ * because DateTimeModule normalizes format on serialization
+ * (e.g. "+0000" โ "Z", "Z" โ ".000Z").
+ */
+ private static boolean areSameDateTime(String expected, String actual) {
+ if (expected == null || actual == null
+ || !ISO_DATE_REGEX.matcher(expected).matches()
+ || !ISO_DATE_REGEX.matcher(actual).matches()) {
+ return false;
+ }
+ return DateTime.parse(expected).equals(DateTime.parse(actual));
+ }
+
+ private static void diffChild(String path, JsonNode expected, JsonNode actual, List diffs) {
+ if (expected == null)
+ diffs.add("ADDED " + path + " = " + summarize(actual));
+ else if (actual == null)
+ diffs.add("MISSING " + path + " (was " + summarize(expected) + ")");
+ else
+ diffNodes(path, expected, actual, diffs);
+ }
+
+ private static String summarize(JsonNode node) {
+ if (node == null) {
+ return "";
+ }
+ String text = node.toString();
+ return text.length() > 80 ? text.substring(0, 77) + "..." : text;
+ }
+}
diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
new file mode 100644
index 000000000..e189b5e2b
--- /dev/null
+++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
@@ -0,0 +1,147 @@
+package com.amazonaws.services.lambda.runtime.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
+import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers;
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode;
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Framework-agnostic assertion utilities for verifying Lambda event
+ * serialization.
+ *
+ *
+ * When opentest4j is on the classpath (e.g. JUnit 5.x / JUnit Platform),
+ * assertion failures are reported as
+ * {@code org.opentest4j.AssertionFailedError}
+ * which enables rich diff support in IDEs. Otherwise, falls back to plain
+ * {@link AssertionError}.
+ *
+ *
+ *
+ * This class is intentionally package-private to support updates to
+ * the aws-lambda-java-events and aws-lambda-java-serialization packages.
+ * Consider making it public if there's a real request for it.
+ *
+ */
+class LambdaEventAssert {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ /**
+ * Round-trip using the registered {@link LambdaEventSerializers} path
+ * (Jackson + mixins + DateModule + DateTimeModule + naming strategies).
+ *
+ *
+ * The check performs two consecutive round-trips
+ * (JSON → POJO → JSON → POJO → JSON) and compares the
+ * original JSON tree against the final output tree. A single structural
+ * comparison catches both:
+ *
+ *
+ * Fields silently dropped during deserialization
+ * Non-idempotent serialization (output changes across round-trips)
+ *
+ *
+ * @param fileName classpath resource name (must end with {@code .json})
+ * @param targetClass the event class to deserialize into
+ * @throws AssertionError if the original and final JSON trees differ
+ */
+ public static void assertSerializationRoundTrip(String fileName, Class targetClass) {
+ PojoSerializer serializer = LambdaEventSerializers.serializerFor(targetClass,
+ ClassLoader.getSystemClassLoader());
+
+ if (!fileName.endsWith(".json")) {
+ throw new IllegalArgumentException("File " + fileName + " must have json extension");
+ }
+
+ byte[] originalBytes;
+ try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) {
+ if (stream == null) {
+ throw new IllegalArgumentException("Could not load resource '" + fileName + "' from classpath");
+ }
+ originalBytes = toBytes(stream);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read resource " + fileName, e);
+ }
+
+ // Two round-trips: original โ POJO โ JSON โ POJO โ JSON
+ // We are doing 2 passes so we can check instability problems
+ // like UnstablePojo in LambdaEventAssertTest
+ ByteArrayOutputStream firstOutput = roundTrip(new ByteArrayInputStream(originalBytes), serializer);
+ ByteArrayOutputStream secondOutput = roundTrip(
+ new ByteArrayInputStream(firstOutput.toByteArray()), serializer);
+
+ // Compare original tree against final tree.
+ // Strip explicit nulls from the original because the serializer is
+ // configured with Include.NON_NULL โ null fields are intentionally
+ // omitted and that is not a data-loss bug.
+ try {
+ JsonNode originalTree = JsonNodeUtils.stripNulls(MAPPER.readTree(originalBytes));
+ JsonNode finalTree = MAPPER.readTree(secondOutput.toByteArray());
+
+ if (!originalTree.equals(finalTree)) {
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", originalTree, finalTree, diffs);
+
+ if (!diffs.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Serialization round-trip failure for ")
+ .append(targetClass.getSimpleName())
+ .append(" (").append(diffs.size()).append(" difference(s)):\n");
+ for (String diff : diffs) {
+ msg.append(" ").append(diff).append('\n');
+ }
+
+ String expected = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(originalTree);
+ String actual = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(finalTree);
+ throw buildAssertionError(msg.toString(), expected, actual);
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to parse JSON for tree comparison", e);
+ }
+ }
+
+ private static ByteArrayOutputStream roundTrip(InputStream stream, PojoSerializer serializer) {
+ T event = serializer.fromJson(stream);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ serializer.toJson(event, outputStream);
+ return outputStream;
+ }
+
+ private static byte[] toBytes(InputStream stream) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] chunk = new byte[4096];
+ int n;
+ while ((n = stream.read(chunk)) != -1) {
+ buffer.write(chunk, 0, n);
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Tries to create an opentest4j AssertionFailedError for rich IDE diff
+ * support. Falls back to plain AssertionError if opentest4j is not on
+ * the classpath.
+ */
+ private static AssertionError buildAssertionError(String message, String expected, String actual) {
+ try {
+ // opentest4j is provided by JUnit Platform (5.x) and enables
+ // IDE diff viewers to show expected vs actual side-by-side.
+ Class> cls = Class.forName("org.opentest4j.AssertionFailedError");
+ return (AssertionError) cls
+ .getConstructor(String.class, Object.class, Object.class)
+ .newInstance(message, expected, actual);
+ } catch (ReflectiveOperationException e) {
+ return new AssertionError(message + "\nExpected:\n" + expected + "\nActual:\n" + actual);
+ }
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java
index 752b84e27..43030bbca 100644
--- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java
@@ -81,6 +81,12 @@ public void testLoadAPIGatewayV2CustomAuthorizerEvent() {
assertThat(event).isNotNull();
assertThat(event.getRequestContext().getHttp().getMethod()).isEqualTo("POST");
+ // getTime() converts the raw string "12/Mar/2020:19:03:58 +0000" into a DateTime object;
+ // Jackson then serializes it as ISO-8601 "2020-03-12T19:03:58.000Z"
+ assertThat(event.getRequestContext().getTime().toInstant().getMillis())
+ .isEqualTo(DateTime.parse("2020-03-12T19:03:58.000Z").toInstant().getMillis());
+ // getTimeEpoch() converts the raw long into an Instant;
+ // Jackson then serializes it as a decimal seconds value
assertThat(event.getRequestContext().getTimeEpoch()).isEqualTo(Instant.ofEpochMilli(1583348638390L));
}
@@ -136,6 +142,9 @@ public void testLoadLexEvent() {
assertThat(event.getCurrentIntent().getName()).isEqualTo("BookHotel");
assertThat(event.getCurrentIntent().getSlots()).hasSize(4);
assertThat(event.getBot().getName()).isEqualTo("BookTrip");
+ // Jackson leniently coerces the JSON number for "Nights" into a String
+ // because slots is typed as Map
+ assertThat(event.getCurrentIntent().getSlots().get("Nights")).isInstanceOf(String.class);
}
@Test
@@ -159,6 +168,10 @@ public void testLoadMSKFirehoseEvent() {
assertThat(event.getRecords().get(0).getKafkaRecordValue().array()).asString().isEqualTo("{\"Name\":\"Hello World\"}");
assertThat(event.getRecords().get(0).getApproximateArrivalTimestamp()).asString().isEqualTo("1716369573887");
assertThat(event.getRecords().get(0).getMskRecordMetadata()).asString().isEqualTo("{offset=0, partitionId=1, approximateArrivalTimestamp=1716369573887}");
+ // Jackson leniently coerces the JSON number in mskRecordMetadata into a String
+ // because the map is typed as Map
+ Map metadata = event.getRecords().get(0).getMskRecordMetadata();
+ assertThat(metadata.get("approximateArrivalTimestamp")).isInstanceOf(String.class);
}
@Test
@@ -408,6 +421,8 @@ public void testLoadRabbitMQEvent() {
.returns("AIDACKCEVSQ6C2EXAMPLE", from(RabbitMQEvent.BasicProperties::getUserId))
.returns(80, from(RabbitMQEvent.BasicProperties::getBodySize))
.returns("Jan 1, 1970, 12:33:41 AM", from(RabbitMQEvent.BasicProperties::getTimestamp));
+ // Jackson leniently coerces the JSON string "60000" for expiration into int
+ // because the model field is typed as int
Map headers = basicProperties.getHeaders();
assertThat(headers).hasSize(3);
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java
new file mode 100644
index 000000000..ec5798dca
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java
@@ -0,0 +1,155 @@
+/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
+package com.amazonaws.services.lambda.runtime.tests;
+
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode;
+import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class JsonNodeUtilsTest {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // --- stripNulls ---
+
+ @Test
+ void stripNulls_removesTopLevelNulls() throws Exception {
+ JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":null,\"c\":\"hello\"}");
+ JsonNode result = JsonNodeUtils.stripNulls(node);
+ assertEquals(MAPPER.readTree("{\"a\":1,\"c\":\"hello\"}"), result);
+ }
+
+ @Test
+ void stripNulls_removesNestedNulls() throws Exception {
+ JsonNode node = MAPPER.readTree("{\"outer\":{\"keep\":true,\"drop\":null}}");
+ JsonNode result = JsonNodeUtils.stripNulls(node);
+ assertEquals(MAPPER.readTree("{\"outer\":{\"keep\":true}}"), result);
+ }
+
+ @Test
+ void stripNulls_leavesArrayElementsIntact() throws Exception {
+ // Nulls inside arrays are kept (they're positional)
+ JsonNode node = MAPPER.readTree("{\"arr\":[1,null,3]}");
+ JsonNode result = JsonNodeUtils.stripNulls(node);
+ assertEquals(MAPPER.readTree("{\"arr\":[1,null,3]}"), result);
+ }
+
+ @Test
+ void stripNulls_removesNullsInsideArrayObjects() throws Exception {
+ JsonNode node = MAPPER.readTree("[{\"a\":1,\"b\":null},{\"c\":null}]");
+ JsonNode result = JsonNodeUtils.stripNulls(node);
+ assertEquals(MAPPER.readTree("[{\"a\":1},{}]"), result);
+ }
+
+ @Test
+ void stripNulls_noOpOnCleanTree() throws Exception {
+ JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":\"two\"}");
+ JsonNode result = JsonNodeUtils.stripNulls(node);
+ assertEquals(MAPPER.readTree("{\"a\":1,\"b\":\"two\"}"), result);
+ }
+
+ // --- diffNodes ---
+
+ @Test
+ void diffNodes_identicalTrees_noDiffs() throws Exception {
+ JsonNode a = MAPPER.readTree("{\"x\":1,\"y\":\"hello\"}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", a, a.deepCopy(), diffs);
+ assertTrue(diffs.isEmpty());
+ }
+
+ @Test
+ void diffNodes_changedValue() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"x\":1}");
+ JsonNode actual = MAPPER.readTree("{\"x\":2}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).startsWith("CHANGED .x"));
+ }
+
+ @Test
+ void diffNodes_missingField() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"a\":1,\"b\":2}");
+ JsonNode actual = MAPPER.readTree("{\"a\":1}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).contains("MISSING") && diffs.get(0).contains(".b"), "got: " + diffs.get(0));
+ }
+
+ @Test
+ void diffNodes_addedField() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"a\":1}");
+ JsonNode actual = MAPPER.readTree("{\"a\":1,\"b\":2}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size(), "diffs: " + diffs);
+ assertTrue(diffs.get(0).contains("ADDED") && diffs.get(0).contains(".b"), "got: " + diffs.get(0));
+ }
+
+ @Test
+ void diffNodes_nestedObjectDiff() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"outer\":{\"inner\":\"old\"}}");
+ JsonNode actual = MAPPER.readTree("{\"outer\":{\"inner\":\"new\"}}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).contains(".outer.inner"));
+ }
+
+ @Test
+ void diffNodes_arrayElementDiff() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"arr\":[1,2,3]}");
+ JsonNode actual = MAPPER.readTree("{\"arr\":[1,99,3]}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).contains("[1]"));
+ }
+
+ @Test
+ void diffNodes_arrayLengthMismatch() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"arr\":[1,2]}");
+ JsonNode actual = MAPPER.readTree("{\"arr\":[1,2,3]}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).contains("ADDED"), "got: " + diffs.get(0));
+ }
+
+ // --- areSameDateTime (tested indirectly via diffNodes) ---
+
+ @Test
+ void diffNodes_equivalentDateTimes_noDiff() throws Exception {
+ // "+0000" vs "Z" โ same instant, different format
+ JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000+0000\"}");
+ JsonNode actual = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertTrue(diffs.isEmpty(), "Expected no diffs for equivalent datetimes, got: " + diffs);
+ }
+
+ @Test
+ void diffNodes_differentDateTimes_hasDiff() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}");
+ JsonNode actual = MAPPER.readTree("{\"t\":\"2021-01-01T00:00:00.000Z\"}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ }
+
+ @Test
+ void diffNodes_nonDateStrings_notTreatedAsDates() throws Exception {
+ JsonNode expected = MAPPER.readTree("{\"s\":\"hello\"}");
+ JsonNode actual = MAPPER.readTree("{\"s\":\"world\"}");
+ List diffs = new ArrayList<>();
+ JsonNodeUtils.diffNodes("", expected, actual, diffs);
+ assertEquals(1, diffs.size());
+ assertTrue(diffs.get(0).startsWith("CHANGED .s"));
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java
new file mode 100644
index 000000000..63067f8b2
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java
@@ -0,0 +1,71 @@
+package com.amazonaws.services.lambda.runtime.tests;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class LambdaEventAssertTest {
+ /**
+ * Demonstrates the completeness check: the fixture has a field
+ * ({@code unknownField}) that {@code PartialPojo} does not capture,
+ * so it gets silently dropped during deserialization.
+ */
+ @Test
+ void shouldFailWhenFieldIsDropped() {
+ AssertionError error = assertThrows(AssertionError.class,
+ () -> LambdaEventAssert.assertSerializationRoundTrip(
+ "partial_pojo.json", PartialPojo.class));
+
+ assertTrue(error.getMessage().contains("PartialPojo"),
+ "Error message should name the failing class");
+ }
+
+ /**
+ * Demonstrates the stability check: the getter mutates state on each
+ * call, so the first and second round-trips produce different JSON.
+ */
+ @Test
+ void shouldFailWhenSerializationIsUnstable() {
+ AssertionError error = assertThrows(AssertionError.class,
+ () -> LambdaEventAssert.assertSerializationRoundTrip(
+ "unstable_pojo.json", UnstablePojo.class));
+
+ assertTrue(error.getMessage().contains("UnstablePojo"),
+ "Error message should name the failing class");
+ }
+
+ /** POJO that only captures {@code name}, silently dropping any other fields. */
+ public static class PartialPojo {
+ private String name;
+
+ public PartialPojo() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ /**
+ * POJO with a getter that appends a suffix, making serialization
+ * non-idempotent.
+ */
+ public static class UnstablePojo {
+ private String name;
+
+ public UnstablePojo() {
+ }
+
+ public String getName() {
+ return name == null ? null : name + "_x";
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java
new file mode 100644
index 000000000..e700b0d01
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java
@@ -0,0 +1,59 @@
+/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
+package com.amazonaws.services.lambda.runtime.tests;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+/**
+ * Verifies serialization round-trip fidelity for Lambda response event types.
+ *
+ * Response events are POJOs that Lambda functions return to the RIC, which
+ * serializes them to JSON via {@code EventHandlerLoader.getSerializer()}.
+ * None of these types are registered in {@code SUPPORTED_EVENTS}, so they
+ * go through the bare Jackson path ({@code JacksonFactory.getInstance()
+ * .getSerializer(type)}).
+ *
+ * Although the RIC only calls {@code toJson()} on response types (never
+ * {@code fromJson()}), the round-trip test is a stricter check: if a response
+ * type survives JSON → POJO → JSON → POJO → JSON, it
+ * certainly survives the production POJO → JSON path.
+ *
+ * @see SerializationRoundTripTest for registered input events
+ * @see UnregisteredEventSerializationRoundTripTest for unregistered input events
+ */
+@SuppressWarnings("deprecation") // APIGatewayV2ProxyResponseEvent is deprecated
+public class ResponseEventSerializationRoundTripTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("passingCases")
+ void roundTrip(String displayName, String fixture, Class> eventClass) {
+ LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass);
+ }
+
+ private static Stream passingCases() {
+ return Stream.of(
+ // API Gateway responses
+ args(APIGatewayProxyResponseEvent.class, "response/apigw_proxy_response.json"),
+ args(APIGatewayV2HTTPResponse.class, "response/apigw_v2_http_response.json"),
+ args(APIGatewayV2WebSocketResponse.class, "response/apigw_v2_websocket_response.json"),
+ args(APIGatewayV2ProxyResponseEvent.class, "response/apigw_v2_websocket_response.json"),
+ // ALB response
+ args(ApplicationLoadBalancerResponseEvent.class, "response/alb_response.json"),
+ // S3 Batch response
+ args(S3BatchResponse.class, "response/s3_batch_response.json"),
+ // SQS Batch response
+ args(SQSBatchResponse.class, "response/sqs_batch_response.json"),
+ // Simple IAM Policy response (HTTP API)
+ args(SimpleIAMPolicyResponse.class, "response/simple_iam_policy_response.json"),
+ // MSK Firehose response
+ args(MSKFirehoseResponse.class, "response/msk_firehose_response.json"));
+ }
+
+ private static Arguments args(Class> clazz, String fixture) {
+ return Arguments.of(clazz.getSimpleName(), fixture, clazz);
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java
new file mode 100644
index 000000000..3eab8fec0
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java
@@ -0,0 +1,108 @@
+/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
+package com.amazonaws.services.lambda.runtime.tests;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Verifies serialization round-trip fidelity for events that are registered in
+ * {@code LambdaEventSerializers.SUPPORTED_EVENTS}.
+ *
+ * Registered events go through the full customized serialization path in the
+ * Runtime Interface Client (RIC): {@code EventHandlerLoader.getSerializer()}
+ * detects them via {@code isLambdaSupportedEvent()} and delegates to
+ * {@code LambdaEventSerializers.serializerFor()}, which applies Jackson mixins,
+ * {@code DateModule}/{@code DateTimeModule}, and naming strategies.
+ *
+ * Each case feeds a JSON fixture through
+ * {@link LambdaEventAssert#assertSerializationRoundTrip} which performs two
+ * consecutive round-trips and compares the original JSON tree against the
+ * final output.
+ *
+ * @see UnregisteredEventSerializationRoundTripTest for events not in SUPPORTED_EVENTS
+ */
+public class SerializationRoundTripTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("passingCases")
+ void roundTrip(String displayName, String fixture, Class> eventClass) {
+ LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass);
+ }
+
+ @ParameterizedTest(name = "{0} (known failure)")
+ @MethodSource("knownFailureCases")
+ void roundTripKnownFailures(String displayName, String fixture, Class> eventClass) {
+ assertThrows(Throwable.class,
+ () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass),
+ displayName + " was expected to fail but passed โ move it to passingCases()");
+ }
+
+ private static Stream passingCases() {
+ return Stream.of(
+ args(CloudFormationCustomResourceEvent.class, "cloudformation_event.json"),
+ args(CloudWatchLogsEvent.class, "cloudwatchlogs_event.json"),
+ args(CodeCommitEvent.class, "codecommit_event.json"),
+ args(ConfigEvent.class, "config_event.json"),
+ args(DynamodbEvent.class, "ddb/dynamo_event_roundtrip.json"),
+ args(KinesisEvent.class, "kinesis/kinesis_event_roundtrip.json"),
+ args(KinesisFirehoseEvent.class, "firehose_event.json"),
+ args(LambdaDestinationEvent.class, "lambda_destination_event.json"),
+ args(ScheduledEvent.class, "cloudwatch_event.json"),
+ args(SecretsManagerRotationEvent.class, "secrets_rotation_event.json"),
+ args(SNSEvent.class, "sns_event.json"),
+ args(LexEvent.class, "lex_event_roundtrip.json"),
+ args(ConnectEvent.class, "connect_event.json"),
+ args(SQSEvent.class, "sqs/sqs_event_nobody.json"),
+ args(APIGatewayProxyRequestEvent.class, "apigw_rest_event.json"),
+ args(CloudFrontEvent.class, "cloudfront_event.json"),
+ args(S3Event.class, "s3_event.json"),
+ args(S3EventNotification.class, "s3_event.json"),
+ args(APIGatewayV2HTTPEvent.class, "apigw_http_event.json"),
+ args(APIGatewayCustomAuthorizerEvent.class, "apigw_auth.json"),
+ args(ApplicationLoadBalancerRequestEvent.class, "elb_event.json"),
+ args(CloudWatchCompositeAlarmEvent.class, "cloudwatch_composite_alarm.json"),
+ args(CloudWatchMetricAlarmEvent.class, "cloudwatch_metric_alarm.json"),
+ args(CognitoUserPoolPreTokenGenerationEventV2.class, "cognito_user_pool_pre_token_generation_event_v2.json"),
+ args(KafkaEvent.class, "kafka_event_roundtrip.json"),
+ args(MSKFirehoseEvent.class, "msk_firehose_event_roundtrip.json"),
+ args(RabbitMQEvent.class, "rabbitmq_event_roundtrip.json"),
+ args(S3BatchEventV2.class, "s3_batch_event_v2.json"),
+ args(IoTButtonEvent.class, "iot_button_event.json"),
+ args(CognitoEvent.class, "cognito_sync_event.json"),
+ args(DynamodbTimeWindowEvent.class, "ddb/dynamo_time_window_event.json"),
+ args(KinesisTimeWindowEvent.class, "kinesis/kinesis_time_window_event.json"));
+ }
+
+ private static Stream knownFailureCases() {
+ return Stream.of(
+ // APIGatewayV2CustomAuthorizerEvent has two serialization issues:
+ // 1. getTime() parses the raw string "12/Mar/2020:19:03:58 +0000" into a
+ // DateTime via dd/MMM/yyyy formatter. Jackson serializes as ISO-8601, but
+ // the formatter cannot parse ISO-8601 back on the second round-trip.
+ // The time field is effectively mandatory (getTime() throws NPE if null),
+ // and the date format change is inherent to how the serialization works.
+ // 2. getTimeEpoch() converts long to Instant; Jackson serializes as decimal
+ // seconds (e.g. 1583348638.390000000) instead of the original long.
+ // Both transformations are lossy; coercion captured in EventLoaderTest.
+ args(APIGatewayV2CustomAuthorizerEvent.class, "apigw_auth_v2.json"),
+ // ActiveMQEvent has one serialization issue:
+ // Destination.physicalName (camelCase) vs JSON "physicalname" (lowercase) โ
+ // ACCEPT_CASE_INSENSITIVE_PROPERTIES is disabled in JacksonFactory so the
+ // field is silently dropped during deserialization.
+ // Fix: create an ActiveMQEventMixin with a DestinationMixin that maps
+ // @JsonProperty("physicalname") to getPhysicalName()/setPhysicalName(),
+ // then register it in LambdaEventSerializers MIXIN_MAP and NESTED_CLASS_MAP.
+ args(ActiveMQEvent.class, "mq_event.json"));
+ }
+
+ private static Arguments args(Class> clazz, String fixture) {
+ return Arguments.of(clazz.getSimpleName(), fixture, clazz);
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java
new file mode 100644
index 000000000..12ad818e4
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java
@@ -0,0 +1,90 @@
+/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
+package com.amazonaws.services.lambda.runtime.tests;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Verifies serialization round-trip fidelity for events that are NOT registered
+ * in {@code LambdaEventSerializers.SUPPORTED_EVENTS}.
+ *
+ * In the Runtime Interface Client (RIC), when a handler's event type is not
+ * in {@code SUPPORTED_EVENTS}, {@code EventHandlerLoader.getSerializer()} falls
+ * through to {@code JacksonFactory.getInstance().getSerializer(type)} โ a bare
+ * {@code PojoSerializer} backed by Jackson without any mixins or naming
+ * strategies. However, {@code LambdaEventSerializers.serializerFor()} (used by
+ * this test) unconditionally registers {@code DateModule} and
+ * {@code DateTimeModule}, so Joda/java.time types are still handled. For most
+ * unregistered events this makes no practical difference because they don't
+ * contain Joda DateTime fields.
+ *
+ * @see SerializationRoundTripTest for events registered in SUPPORTED_EVENTS
+ */
+@SuppressWarnings("deprecation") // APIGatewayV2ProxyRequestEvent is deprecated
+public class UnregisteredEventSerializationRoundTripTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("passingCases")
+ void roundTrip(String displayName, String fixture, Class> eventClass) {
+ LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass);
+ }
+
+ @ParameterizedTest(name = "{0} (known failure)")
+ @MethodSource("knownFailureCases")
+ void roundTripKnownFailures(String displayName, String fixture, Class> eventClass) {
+ assertThrows(Throwable.class,
+ () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass),
+ displayName + " was expected to fail but passed โ move it to passingCases()");
+ }
+
+ private static Stream passingCases() {
+ return Stream.of(
+ // S3 Batch
+ args(S3BatchEvent.class, "s3_batch_event.json"),
+ // AppSync
+ args(AppSyncLambdaAuthorizerEvent.class, "appsync_authorizer_event.json"),
+ args(AppSyncLambdaAuthorizerResponse.class, "appsync_authorizer_response.json"),
+ // TimeWindow response
+ args(TimeWindowEventResponse.class, "time_window_event_response.json"),
+ // Cognito UserPool triggers
+ args(CognitoUserPoolPreSignUpEvent.class, "cognito/cognito_userpool_presignup.json"),
+ args(CognitoUserPoolPostConfirmationEvent.class, "cognito/cognito_userpool_postconfirmation.json"),
+ args(CognitoUserPoolPreAuthenticationEvent.class, "cognito/cognito_userpool_preauthentication.json"),
+ args(CognitoUserPoolPostAuthenticationEvent.class, "cognito/cognito_userpool_postauthentication.json"),
+ args(CognitoUserPoolDefineAuthChallengeEvent.class, "cognito/cognito_userpool_define_auth_challenge.json"),
+ args(CognitoUserPoolCreateAuthChallengeEvent.class, "cognito/cognito_userpool_create_auth_challenge.json"),
+ args(CognitoUserPoolVerifyAuthChallengeResponseEvent.class, "cognito/cognito_userpool_verify_auth_challenge.json"),
+ args(CognitoUserPoolMigrateUserEvent.class, "cognito/cognito_userpool_migrate_user.json"),
+ args(CognitoUserPoolCustomMessageEvent.class, "cognito/cognito_userpool_custom_message.json"),
+ args(CognitoUserPoolPreTokenGenerationEvent.class, "cognito/cognito_userpool_pre_token_generation.json"),
+ // Kinesis Analytics
+ args(KinesisAnalyticsFirehoseInputPreprocessingEvent.class, "kinesis/kinesis_analytics_firehose_input_preprocessing.json"),
+ args(KinesisAnalyticsStreamsInputPreprocessingEvent.class, "kinesis/kinesis_analytics_streams_input_preprocessing.json"),
+ args(KinesisAnalyticsInputPreprocessingResponse.class, "kinesis/kinesis_analytics_input_preprocessing_response.json"),
+ args(KinesisAnalyticsOutputDeliveryEvent.class, "kinesis/kinesis_analytics_output_delivery.json"),
+ args(KinesisAnalyticsOutputDeliveryResponse.class, "kinesis/kinesis_analytics_output_delivery_response.json"),
+ // API Gateway V2 WebSocket
+ args(APIGatewayV2WebSocketEvent.class, "apigw_websocket_event.json"),
+ args(APIGatewayV2ProxyRequestEvent.class, "apigw_websocket_event.json"));
+ }
+
+ private static Stream knownFailureCases() {
+ return Stream.of(
+ // S3ObjectLambdaEvent: Lombok generates getXAmzRequestId() for field
+ // "xAmzRequestId". With USE_STD_BEAN_NAMING, Jackson derives the property
+ // name as "XAmzRequestId" (capital X), so the original "xAmzRequestId" key
+ // is silently dropped during deserialization.
+ // Fix: add @JsonProperty("xAmzRequestId") on the field or getter.
+ args(S3ObjectLambdaEvent.class, "s3_object_lambda_event.json"));
+ }
+
+ private static Arguments args(Class> clazz, String fixture) {
+ return Arguments.of(clazz.getSimpleName(), fixture, clazz);
+ }
+}
diff --git a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json
index 88f4e5b4b..91954656c 100644
--- a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json
+++ b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json
@@ -18,18 +18,6 @@
"requestContext": {
"accountId": "123456789012",
"apiId": "api-id",
- "authentication": {
- "clientCert": {
- "clientCertPem": "CERT_CONTENT",
- "subjectDN": "www.example.com",
- "issuerDN": "Example issuer",
- "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
- "validity": {
- "notBefore": "May 28 12:30:02 2019 GMT",
- "notAfter": "Aug 5 09:36:04 2021 GMT"
- }
- }
- },
"authorizer": {
"jwt": {
"claims": {
diff --git a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json
index 28f10c221..a139ccbe8 100644
--- a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json
+++ b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json
@@ -14,19 +14,22 @@
"Header2": [
"value1",
"value2"
+ ],
+ "Header3": [
+ "value1,value2"
]
},
"queryStringParameters": {
"parameter1": "value1",
- "parameter2": "value"
+ "parameter2": "value2"
},
"multiValueQueryStringParameters": {
"parameter1": [
- "value1",
- "value2"
+ "value1"
],
"parameter2": [
- "value"
+ "value1",
+ "value2"
]
},
"requestContext": {
@@ -52,17 +55,7 @@
"sourceIp": "IP",
"user": null,
"userAgent": "user-agent",
- "userArn": null,
- "clientCert": {
- "clientCertPem": "CERT_CONTENT",
- "subjectDN": "www.example.com",
- "issuerDN": "Example issuer",
- "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
- "validity": {
- "notBefore": "May 28 12:30:02 2019 GMT",
- "notAfter": "Aug 5 09:36:04 2021 GMT"
- }
- }
+ "userArn": null
},
"path": "/my/path",
"protocol": "HTTP/1.1",
@@ -76,5 +69,5 @@
"pathParameters": null,
"stageVariables": null,
"body": "Hello from Lambda!",
- "isBase64Encoded": true
+ "isBase64Encoded": false
}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json
new file mode 100644
index 000000000..a47fc3a94
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json
@@ -0,0 +1,88 @@
+{
+ "resource": "/",
+ "path": "/",
+ "httpMethod": "GET",
+ "headers": {
+ "Host": "abcdef1234.execute-api.us-east-1.amazonaws.com",
+ "Sec-WebSocket-Extensions": "permessage-deflate",
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
+ "Sec-WebSocket-Version": "13",
+ "X-Forwarded-For": "192.0.2.1",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ "multiValueHeaders": {
+ "Host": [
+ "abcdef1234.execute-api.us-east-1.amazonaws.com"
+ ],
+ "Sec-WebSocket-Extensions": [
+ "permessage-deflate"
+ ],
+ "Sec-WebSocket-Key": [
+ "dGhlIHNhbXBsZSBub25jZQ=="
+ ],
+ "Sec-WebSocket-Version": [
+ "13"
+ ],
+ "X-Forwarded-For": [
+ "192.0.2.1"
+ ],
+ "X-Forwarded-Port": [
+ "443"
+ ],
+ "X-Forwarded-Proto": [
+ "https"
+ ]
+ },
+ "queryStringParameters": {
+ "param1": "value1"
+ },
+ "multiValueQueryStringParameters": {
+ "param1": [
+ "value1"
+ ]
+ },
+ "pathParameters": {
+ "proxy": "path/to/resource"
+ },
+ "stageVariables": {
+ "stageVar1": "value1"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "abcdef",
+ "stage": "prod",
+ "requestId": "abc-def-ghi",
+ "identity": {
+ "cognitoIdentityPoolId": "us-east-1:id-pool",
+ "accountId": "123456789012",
+ "cognitoIdentityId": "us-east-1:identity-id",
+ "caller": "caller-id",
+ "apiKey": "api-key-id",
+ "sourceIp": "192.0.2.1",
+ "cognitoAuthenticationType": "authenticated",
+ "cognitoAuthenticationProvider": "provider",
+ "userArn": "arn:aws:iam::123456789012:user/testuser",
+ "userAgent": "Mozilla/5.0",
+ "user": "testuser",
+ "accessKey": "AKIAIOSFODNN7EXAMPLE"
+ },
+ "resourcePath": "/",
+ "httpMethod": "GET",
+ "apiId": "abcdef1234",
+ "connectedAt": 1583348638390,
+ "connectionId": "abc123=",
+ "domainName": "abcdef1234.execute-api.us-east-1.amazonaws.com",
+ "eventType": "CONNECT",
+ "extendedRequestId": "abc123=",
+ "integrationLatency": "100",
+ "messageDirection": "IN",
+ "messageId": "msg-001",
+ "requestTime": "09/Apr/2020:18:03:58 +0000",
+ "requestTimeEpoch": 1583348638390,
+ "routeKey": "$connect",
+ "status": "200"
+ },
+ "body": "request body",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json
new file mode 100644
index 000000000..494a500f3
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json
@@ -0,0 +1,15 @@
+{
+ "authorizationToken": "BE9DC5E3-D410-4733-AF76-70178092E681",
+ "requestContext": {
+ "apiId": "giy7kumfmvcqvbedntjwjvagii",
+ "accountId": "254688921111",
+ "requestId": "b80ed838-14c6-4500-b4c3-b694c7bef086",
+ "queryDocument": "mutation MyNewTask($desc: String!) {\n createTask(description: $desc) {\n id\n }\n}\n",
+ "operationName": "MyNewTask",
+ "variables": {}
+ },
+ "requestHeaders": {
+ "host": "giy7kumfmvcqvbedntjwjvagii.appsync-api.us-east-1.amazonaws.com",
+ "content-type": "application/json"
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json
new file mode 100644
index 000000000..1216ad51d
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json
@@ -0,0 +1,11 @@
+{
+ "isAuthorized": true,
+ "resolverContext": {
+ "name": "Foo Man",
+ "balance": "100"
+ },
+ "deniedFields": [
+ "Mutation.createEvent"
+ ],
+ "ttlOverride": 15
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json
index 7485310e9..bf4625d06 100644
--- a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json
+++ b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json
@@ -7,7 +7,6 @@
},
"request": {
"uri": "/test",
- "querystring": "auth=test&foo=bar",
"method": "GET",
"clientIp": "2001:cdba::3257:9652",
"headers": {
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json
new file mode 100644
index 000000000..495b41475
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json
@@ -0,0 +1,37 @@
+{
+ "version": "1",
+ "triggerSource": "CreateAuthChallenge_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "challengeName": "CUSTOM_CHALLENGE",
+ "session": [
+ {
+ "challengeName": "PASSWORD_VERIFIER",
+ "challengeResult": true,
+ "challengeMetadata": "metadata1"
+ }
+ ],
+ "userNotFound": false
+ },
+ "response": {
+ "publicChallengeParameters": {
+ "captchaUrl": "url/123.jpg"
+ },
+ "privateChallengeParameters": {
+ "answer": "5"
+ },
+ "challengeMetadata": "CAPTCHA_CHALLENGE"
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json
new file mode 100644
index 000000000..aa7a53a83
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json
@@ -0,0 +1,28 @@
+{
+ "version": "1",
+ "triggerSource": "CustomMessage_SignUp",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com",
+ "phone_number_verified": "true",
+ "email_verified": "true"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "codeParameter": "####",
+ "usernameParameter": "testuser"
+ },
+ "response": {
+ "smsMessage": "Your code is ####",
+ "emailMessage": "Your code is ####",
+ "emailSubject": "Welcome"
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json
new file mode 100644
index 000000000..e320b71d1
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json
@@ -0,0 +1,32 @@
+{
+ "version": "1",
+ "triggerSource": "DefineAuthChallenge_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "session": [
+ {
+ "challengeName": "PASSWORD_VERIFIER",
+ "challengeResult": true,
+ "challengeMetadata": "metadata1"
+ }
+ ],
+ "userNotFound": false
+ },
+ "response": {
+ "challengeName": "CUSTOM_CHALLENGE",
+ "issueTokens": false,
+ "failAuthentication": false
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json
new file mode 100644
index 000000000..2897ae063
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json
@@ -0,0 +1,35 @@
+{
+ "version": "1",
+ "triggerSource": "UserMigration_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "validationData": {
+ "key1": "val1"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "userName": "testuser",
+ "password": "test-password"
+ },
+ "response": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "finalUserStatus": "CONFIRMED",
+ "messageAction": "SUPPRESS",
+ "desiredDeliveryMediums": [
+ "EMAIL"
+ ],
+ "forceAliasCreation": false
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json
new file mode 100644
index 000000000..f41084d54
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json
@@ -0,0 +1,20 @@
+{
+ "version": "1",
+ "triggerSource": "PostAuthentication_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "newDeviceUsed": false
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json
new file mode 100644
index 000000000..ecf63c7d3
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json
@@ -0,0 +1,20 @@
+{
+ "version": "1",
+ "triggerSource": "PostConfirmation_ConfirmSignUp",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com",
+ "email_verified": "true"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ }
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json
new file mode 100644
index 000000000..f81ffb902
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json
@@ -0,0 +1,48 @@
+{
+ "version": "1",
+ "triggerSource": "TokenGeneration_HostedAuth",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "groupConfiguration": {
+ "groupsToOverride": [
+ "group1",
+ "group2"
+ ],
+ "iamRolesToOverride": [
+ "arn:aws:iam::123456789012:role/role1"
+ ],
+ "preferredRole": "arn:aws:iam::123456789012:role/role1"
+ }
+ },
+ "response": {
+ "claimsOverrideDetails": {
+ "claimsToAddOrOverride": {
+ "custom:myattr": "myvalue"
+ },
+ "claimsToSuppress": [
+ "email"
+ ],
+ "groupOverrideDetails": {
+ "groupsToOverride": [
+ "group1"
+ ],
+ "iamRolesToOverride": [
+ "arn:aws:iam::123456789012:role/role1"
+ ],
+ "preferredRole": "arn:aws:iam::123456789012:role/role1"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json
new file mode 100644
index 000000000..1402c4684
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json
@@ -0,0 +1,20 @@
+{
+ "version": "1",
+ "triggerSource": "PreAuthentication_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "validationData": {
+ "key1": "val1"
+ },
+ "userNotFound": false
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json
new file mode 100644
index 000000000..0d1f0936a
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json
@@ -0,0 +1,27 @@
+{
+ "version": "1",
+ "triggerSource": "PreSignUp_SignUp",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "validationData": {
+ "key1": "val1"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ }
+ },
+ "response": {
+ "autoConfirmUser": false,
+ "autoVerifyPhone": false,
+ "autoVerifyEmail": false
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json
new file mode 100644
index 000000000..ef14c4ddf
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json
@@ -0,0 +1,27 @@
+{
+ "version": "1",
+ "triggerSource": "VerifyAuthChallengeResponse_Authentication",
+ "region": "us-east-1",
+ "userPoolId": "us-east-1_uPoolId",
+ "userName": "testuser",
+ "callerContext": {
+ "awsSdkVersion": "2.0.0",
+ "clientId": "abcdefg1234567"
+ },
+ "request": {
+ "userAttributes": {
+ "email": "user@example.com"
+ },
+ "clientMetadata": {
+ "meta1": "value1"
+ },
+ "privateChallengeParameters": {
+ "answer": "5"
+ },
+ "challengeAnswer": "5",
+ "userNotFound": false
+ },
+ "response": {
+ "answerCorrect": true
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json
new file mode 100644
index 000000000..6edf1c246
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json
@@ -0,0 +1,20 @@
+{
+ "version": 2,
+ "eventType": "SyncTrigger",
+ "region": "us-east-1",
+ "identityPoolId": "us-east-1:example-identity-pool-id",
+ "identityId": "us-east-1:example-identity-id",
+ "datasetName": "SampleDataset",
+ "datasetRecords": {
+ "SampleKey1": {
+ "oldValue": "oldValue1",
+ "newValue": "newValue1",
+ "op": "replace"
+ },
+ "SampleKey2": {
+ "oldValue": "oldValue2",
+ "newValue": "newValue2",
+ "op": "replace"
+ }
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/connect_event.json b/aws-lambda-java-tests/src/test/resources/connect_event.json
index b71bf6692..4ce17a657 100644
--- a/aws-lambda-java-tests/src/test/resources/connect_event.json
+++ b/aws-lambda-java-tests/src/test/resources/connect_event.json
@@ -12,15 +12,6 @@
"InitialContactId": "6ca32fbd-8f92-46af-92a5-6b0f970f0efe",
"InitiationMethod": "API",
"InstanceARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa",
- "MediaStreams": {
- "Customer": {
- "Audio": {
- "StartFragmentNumber": "91343852333181432392682062622220590765191907586",
- "StartTimestamp": "1565781909613",
- "StreamARN": "arn:aws:kinesisvideo:eu-central-1:123456789012:stream/connect-contact-a3d73b84-ce0e-479a-a9dc-5637c9d30ac9/1565272947806"
- }
- }
- },
"PreviousContactId": "4ca32fbd-8f92-46af-92a5-6b0f970f0efe",
"Queue": {
"Name": "SampleQueue",
diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json
new file mode 100644
index 000000000..10d963c3c
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json
@@ -0,0 +1,97 @@
+{
+ "Records": [
+ {
+ "eventID": "c4ca4238a0b923820dcc509a6f75849b",
+ "eventName": "INSERT",
+ "eventVersion": "1.1",
+ "eventSource": "aws:dynamodb",
+ "awsRegion": "eu-central-1",
+ "dynamodb": {
+ "Keys": {
+ "Id": {
+ "N": "101"
+ }
+ },
+ "NewImage": {
+ "Message": {
+ "S": "New item!"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "ApproximateCreationDateTime": 1.4285376E9,
+ "SequenceNumber": "4421584500000000017450439091",
+ "SizeBytes": 26,
+ "StreamViewType": "NEW_AND_OLD_IMAGES"
+ },
+ "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899",
+ "userIdentity": {
+ "principalId": "dynamodb.amazonaws.com",
+ "type": "Service"
+ }
+ },
+ {
+ "eventID": "c81e728d9d4c2f636f067f89cc14862c",
+ "eventName": "MODIFY",
+ "eventVersion": "1.1",
+ "eventSource": "aws:dynamodb",
+ "awsRegion": "eu-central-1",
+ "dynamodb": {
+ "Keys": {
+ "Id": {
+ "N": "101"
+ }
+ },
+ "NewImage": {
+ "Message": {
+ "S": "This item has changed"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "OldImage": {
+ "Message": {
+ "S": "New item!"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "ApproximateCreationDateTime": 1.635734407123E9,
+ "SequenceNumber": "4421584500000000017450439092",
+ "SizeBytes": 59,
+ "StreamViewType": "NEW_AND_OLD_IMAGES"
+ },
+ "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899"
+ },
+ {
+ "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3",
+ "eventName": "REMOVE",
+ "eventVersion": "1.1",
+ "eventSource": "aws:dynamodb",
+ "awsRegion": "eu-central-1",
+ "dynamodb": {
+ "Keys": {
+ "Id": {
+ "N": "101"
+ }
+ },
+ "OldImage": {
+ "Message": {
+ "S": "This item has changed"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "ApproximateCreationDateTime": 1.4285376E9,
+ "SequenceNumber": "4421584500000000017450439093",
+ "SizeBytes": 38,
+ "StreamViewType": "NEW_AND_OLD_IMAGES"
+ },
+ "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json
new file mode 100644
index 000000000..d931acb80
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json
@@ -0,0 +1,75 @@
+{
+ "Records": [
+ {
+ "eventID": "1",
+ "eventName": "INSERT",
+ "eventVersion": "1.0",
+ "eventSource": "aws:dynamodb",
+ "awsRegion": "us-east-1",
+ "dynamodb": {
+ "Keys": {
+ "Id": {
+ "N": "101"
+ }
+ },
+ "NewImage": {
+ "Message": {
+ "S": "New item!"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "SequenceNumber": "111",
+ "SizeBytes": 26,
+ "StreamViewType": "NEW_AND_OLD_IMAGES"
+ },
+ "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899"
+ },
+ {
+ "eventID": "2",
+ "eventName": "MODIFY",
+ "eventVersion": "1.0",
+ "eventSource": "aws:dynamodb",
+ "awsRegion": "us-east-1",
+ "dynamodb": {
+ "Keys": {
+ "Id": {
+ "N": "101"
+ }
+ },
+ "NewImage": {
+ "Message": {
+ "S": "This item has changed"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "OldImage": {
+ "Message": {
+ "S": "New item!"
+ },
+ "Id": {
+ "N": "101"
+ }
+ },
+ "SequenceNumber": "222",
+ "SizeBytes": 59,
+ "StreamViewType": "NEW_AND_OLD_IMAGES"
+ },
+ "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899"
+ }
+ ],
+ "window": {
+ "start": "2020-07-30T17:00:00Z",
+ "end": "2020-07-30T17:05:00Z"
+ },
+ "state": {
+ "1": "state1"
+ },
+ "shardId": "shard123456789",
+ "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899",
+ "isFinalInvokeForWindow": false,
+ "isWindowTerminatedEarly": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/iot_button_event.json b/aws-lambda-java-tests/src/test/resources/iot_button_event.json
new file mode 100644
index 000000000..8dc82826b
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/iot_button_event.json
@@ -0,0 +1,5 @@
+{
+ "serialNumber": "G030JF055364XVRB",
+ "clickType": "SINGLE",
+ "batteryVoltage": "2000mV"
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json
new file mode 100644
index 000000000..d9f682e5f
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json
@@ -0,0 +1,22 @@
+{
+ "eventSource": "aws:kafka",
+ "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929",
+ "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092",
+ "records": {
+ "mytopic-01": [
+ {
+ "topic": "mytopic",
+ "partition": 0,
+ "offset": 15,
+ "timestamp": 1596480920837,
+ "timestampType": "CREATE_TIME",
+ "value": "SGVsbG8gZnJvbSBLYWZrYSAhIQ==",
+ "headers": [
+ {
+ "headerKey": "aGVhZGVyVmFsdWU="
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json
new file mode 100644
index 000000000..8c6cfe514
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json
@@ -0,0 +1,14 @@
+{
+ "invocationId": "invocationIdExample",
+ "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app",
+ "streamArn": "arn:aws:firehose:us-east-1:123456789012:deliverystream/my-stream",
+ "records": [
+ {
+ "recordId": "49546986683135544286507457936321625675700192471156785154",
+ "kinesisFirehoseRecordMetadata": {
+ "approximateArrivalTimestamp": 1583348638390
+ },
+ "data": "SGVsbG8gV29ybGQ="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json
new file mode 100644
index 000000000..f5be190ec
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json
@@ -0,0 +1,9 @@
+{
+ "records": [
+ {
+ "recordId": "49546986683135544286507457936321625675700192471156785154",
+ "result": "Ok",
+ "data": "SGVsbG8gV29ybGQ="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json
new file mode 100644
index 000000000..573b6baba
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json
@@ -0,0 +1,13 @@
+{
+ "invocationId": "invocationIdExample",
+ "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app",
+ "records": [
+ {
+ "recordId": "49546986683135544286507457936321625675700192471156785154",
+ "lambdaDeliveryRecordMetadata": {
+ "retryHint": 0
+ },
+ "data": "SGVsbG8gV29ybGQ="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json
new file mode 100644
index 000000000..56ddaa194
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json
@@ -0,0 +1,8 @@
+{
+ "records": [
+ {
+ "recordId": "49546986683135544286507457936321625675700192471156785154",
+ "result": "Ok"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json
new file mode 100644
index 000000000..4ae8f3705
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json
@@ -0,0 +1,17 @@
+{
+ "invocationId": "invocationIdExample",
+ "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app",
+ "streamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/my-stream",
+ "records": [
+ {
+ "recordId": "49546986683135544286507457936321625675700192471156785154",
+ "kinesisStreamRecordMetadata": {
+ "sequenceNumber": "49546986683135544286507457936321625675700192471156785154",
+ "partitionKey": "partKey",
+ "shardId": "shardId-000000000000",
+ "approximateArrivalTimestamp": 1583348638390
+ },
+ "data": "SGVsbG8gV29ybGQ="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json
new file mode 100644
index 000000000..e2081ef2b
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json
@@ -0,0 +1,21 @@
+{
+ "Records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1.4285376E9,
+ "encryptionType": "NONE"
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json
new file mode 100644
index 000000000..2d6283c58
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json
@@ -0,0 +1,32 @@
+{
+ "Records": [
+ {
+ "kinesis": {
+ "kinesisSchemaVersion": "1.0",
+ "partitionKey": "1",
+ "sequenceNumber": "49590338271490256608559692538361571095921575989136588898",
+ "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg==",
+ "approximateArrivalTimestamp": 1.607497475E9
+ },
+ "eventSource": "aws:kinesis",
+ "eventVersion": "1.0",
+ "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588898",
+ "eventName": "aws:kinesis:record",
+ "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role",
+ "awsRegion": "us-east-1",
+ "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream"
+ }
+ ],
+ "window": {
+ "start": "2020-12-09T07:04:00Z",
+ "end": "2020-12-09T07:06:00Z"
+ },
+ "state": {
+ "1": "282",
+ "2": "715"
+ },
+ "shardId": "shardId-000000000006",
+ "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream",
+ "isFinalInvokeForWindow": false,
+ "isWindowTerminatedEarly": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json
new file mode 100644
index 000000000..be065eb3f
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json
@@ -0,0 +1,24 @@
+{
+ "messageVersion": "1.0",
+ "invocationSource": "DialogCodeHook",
+ "userId": "John",
+ "sessionAttributes": {
+ "key": "value"
+ },
+ "bot": {
+ "name": "BookTrip",
+ "alias": "$LATEST",
+ "version": "$LATEST"
+ },
+ "outputDialogMode": "Text",
+ "currentIntent": {
+ "name": "BookHotel",
+ "slots": {
+ "Location": "Chicago",
+ "CheckInDate": "2030-11-08",
+ "Nights": "4",
+ "RoomType": "queen"
+ },
+ "confirmationStatus": "None"
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/mq_event.json b/aws-lambda-java-tests/src/test/resources/mq_event.json
index 6505a22d4..8b12af72e 100644
--- a/aws-lambda-java-tests/src/test/resources/mq_event.json
+++ b/aws-lambda-java-tests/src/test/resources/mq_event.json
@@ -5,15 +5,17 @@
{
"messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1",
"messageType": "jms/text-message",
- "data": "QUJDOkFBQUE=",
- "connectionId": "myJMSCoID",
+ "timestamp": 1598827811958,
+ "deliveryMode": 0,
"redelivered": false,
+ "expiration": 0,
+ "priority": 0,
+ "data": "QUJDOkFBQUE=",
+ "brokerInTime": 1598827811958,
+ "brokerOutTime": 1598827811959,
"destination": {
"physicalname": "testQueue"
},
- "timestamp": 1598827811958,
- "brokerInTime": 1598827811958,
- "brokerOutTime": 1598827811959,
"properties": {
"testKey": "testValue"
}
@@ -21,15 +23,17 @@
{
"messageID": "ID:b-8bcfa572-428a-4642-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1",
"messageType": "jms/bytes-message",
+ "timestamp": 1598827811958,
+ "deliveryMode": 0,
+ "redelivered": false,
+ "expiration": 0,
+ "priority": 0,
"data": "3DTOOW7crj51prgVLQaGQ82S48k=",
- "connectionId": "myJMSCoID1",
- "persistent": false,
+ "brokerInTime": 1598827811958,
+ "brokerOutTime": 1598827811959,
"destination": {
"physicalname": "testQueue"
},
- "timestamp": 1598827811958,
- "brokerInTime": 1598827811958,
- "brokerOutTime": 1598827811959,
"properties": {
"testKey": "testValue"
}
diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json
index 6b839912d..140908250 100644
--- a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json
+++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json
@@ -15,4 +15,4 @@
"kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ=="
}
]
-}
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json
new file mode 100644
index 000000000..81b0a9c81
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json
@@ -0,0 +1,18 @@
+{
+ "invocationId": "12345621-4787-0000-a418-36e56Example",
+ "sourceMSKArn": "arn:aws:kafka:EXAMPLE",
+ "deliveryStreamArn": "arn:aws:firehose:EXAMPLE",
+ "region": "us-east-1",
+ "records": [
+ {
+ "recordId": "00000000000000000000000000000000000000000000000000000000000000",
+ "approximateArrivalTimestamp": 1716369573887,
+ "mskRecordMetadata": {
+ "offset": "0",
+ "partitionId": "1",
+ "approximateArrivalTimestamp": "1716369573887"
+ },
+ "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ=="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/partial_pojo.json b/aws-lambda-java-tests/src/test/resources/partial_pojo.json
new file mode 100644
index 000000000..398218039
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/partial_pojo.json
@@ -0,0 +1,4 @@
+{
+ "name": "test",
+ "unknownField": "this will be dropped"
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json
new file mode 100644
index 000000000..44edf2f0a
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json
@@ -0,0 +1,51 @@
+{
+ "eventSource": "aws:rmq",
+ "eventSourceArn": "arn:aws:mq:us-west-2:112556298976:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8",
+ "rmqMessagesByQueue": {
+ "test::/": [
+ {
+ "basicProperties": {
+ "contentType": "text/plain",
+ "contentEncoding": null,
+ "headers": {
+ "header1": {
+ "bytes": [
+ 118,
+ 97,
+ 108,
+ 117,
+ 101,
+ 49
+ ]
+ },
+ "header2": {
+ "bytes": [
+ 118,
+ 97,
+ 108,
+ 117,
+ 101,
+ 50
+ ]
+ },
+ "numberInHeader": 10
+ },
+ "deliveryMode": 1,
+ "priority": 34,
+ "correlationId": null,
+ "replyTo": null,
+ "expiration": 60000,
+ "messageId": null,
+ "timestamp": "Jan 1, 1970, 12:33:41 AM",
+ "type": null,
+ "userId": "AIDACKCEVSQ6C2EXAMPLE",
+ "appId": null,
+ "clusterId": null,
+ "bodySize": 80
+ },
+ "redelivered": false,
+ "data": "eyJ0aW1lb3V0IjowLCJkYXRhIjoiQ1pybWYwR3c4T3Y0YnFMUXhENEUifQ=="
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/alb_response.json b/aws-lambda-java-tests/src/test/resources/response/alb_response.json
new file mode 100644
index 000000000..355eb193a
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/alb_response.json
@@ -0,0 +1,15 @@
+{
+ "statusCode": 200,
+ "statusDescription": "200 OK",
+ "headers": {
+ "Content-Type": "text/html"
+ },
+ "multiValueHeaders": {
+ "Set-Cookie": [
+ "cookie1=value1",
+ "cookie2=value2"
+ ]
+ },
+ "body": "Hello",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json
new file mode 100644
index 000000000..640ccdc5c
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json
@@ -0,0 +1,15 @@
+{
+ "statusCode": 200,
+ "headers": {
+ "Content-Type": "application/json",
+ "X-Custom-Header": "custom-value"
+ },
+ "multiValueHeaders": {
+ "Set-Cookie": [
+ "cookie1=value1",
+ "cookie2=value2"
+ ]
+ },
+ "body": "{\"message\":\"Hello from Lambda\"}",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json
new file mode 100644
index 000000000..c39236650
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json
@@ -0,0 +1,16 @@
+{
+ "statusCode": 200,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "multiValueHeaders": {
+ "Set-Cookie": [
+ "cookie1=value1"
+ ]
+ },
+ "cookies": [
+ "session=abc123; Secure; HttpOnly"
+ ],
+ "body": "{\"message\":\"OK\"}",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json
new file mode 100644
index 000000000..08392e890
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json
@@ -0,0 +1,14 @@
+{
+ "statusCode": 200,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "multiValueHeaders": {
+ "X-Custom": [
+ "val1",
+ "val2"
+ ]
+ },
+ "body": "{\"action\":\"sendmessage\",\"data\":\"hello\"}",
+ "isBase64Encoded": false
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json
new file mode 100644
index 000000000..9ac497624
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json
@@ -0,0 +1,9 @@
+{
+ "records": [
+ {
+ "recordId": "record-1",
+ "result": "Ok",
+ "kafkaRecordValue": "dHJhbnNmb3JtZWQgZGF0YQ=="
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json
new file mode 100644
index 000000000..e63439d84
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json
@@ -0,0 +1,12 @@
+{
+ "invocationSchemaVersion": "1.0",
+ "treatMissingKeysAs": "PermanentFailure",
+ "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo",
+ "results": [
+ {
+ "taskId": "dGFza2lkZ29lc2hlcmUK",
+ "resultCode": "Succeeded",
+ "resultString": "Successfully processed"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json
new file mode 100644
index 000000000..5f23b6405
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json
@@ -0,0 +1,7 @@
+{
+ "isAuthorized": true,
+ "context": {
+ "userId": "user-123",
+ "scope": "read:all"
+ }
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json
new file mode 100644
index 000000000..5ef2de697
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json
@@ -0,0 +1,7 @@
+{
+ "batchItemFailures": [
+ {
+ "itemIdentifier": "059f36b4-87a3-44ab-83d2-661975830a7d"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/s3_batch_event.json b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json
new file mode 100644
index 000000000..a70af0fdd
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json
@@ -0,0 +1,15 @@
+{
+ "invocationSchemaVersion": "1.0",
+ "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo",
+ "job": {
+ "id": "f3cc4f60-61f6-4a2b-8a21-d07600c373ce"
+ },
+ "tasks": [
+ {
+ "taskId": "dGFza2lkZ29lc2hlcmUK",
+ "s3Key": "customerImage1.jpg",
+ "s3VersionId": "1",
+ "s3BucketArn": "arn:aws:s3:::amzn-s3-demo-bucket"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/s3_event.json b/aws-lambda-java-tests/src/test/resources/s3_event.json
index 89e0bd312..73f59d072 100644
--- a/aws-lambda-java-tests/src/test/resources/s3_event.json
+++ b/aws-lambda-java-tests/src/test/resources/s3_event.json
@@ -28,8 +28,10 @@
},
"object": {
"key": "test/key",
+ "urlDecodedKey": "test/key",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
+ "versionId": "",
"sequencer": "0A1B2C3D4E5F678901"
}
}
diff --git a/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json
new file mode 100644
index 000000000..db996e71c
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json
@@ -0,0 +1,29 @@
+{
+ "xAmzRequestId": "requestId",
+ "getObjectContext": {
+ "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=snip",
+ "outputRoute": "io-use1-001",
+ "outputToken": "OutputToken"
+ },
+ "configuration": {
+ "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap",
+ "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap",
+ "payload": "{}"
+ },
+ "userRequest": {
+ "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example",
+ "headers": {
+ "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com",
+ "Accept-Encoding": "identity",
+ "X-Amz-Content-SHA256": "e3b0c44298fc1example"
+ }
+ },
+ "userIdentity": {
+ "type": "AssumedRole",
+ "principalId": "principalId",
+ "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example",
+ "accountId": "111122223333",
+ "accessKeyId": "accessKeyId"
+ },
+ "protocolVersion": "1.00"
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/time_window_event_response.json b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json
new file mode 100644
index 000000000..3c77b3784
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json
@@ -0,0 +1,10 @@
+{
+ "state": {
+ "totalAmount": "500"
+ },
+ "batchItemFailures": [
+ {
+ "itemIdentifier": "49590338271490256608559692538361571095921575989136588898"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aws-lambda-java-tests/src/test/resources/unstable_pojo.json b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json
new file mode 100644
index 000000000..19db9a1cc
--- /dev/null
+++ b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json
@@ -0,0 +1,3 @@
+{
+ "name": "test"
+}
\ No newline at end of file
From 3d9661ee89c92f10a52352ad41a5fe3343ddd832 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 30 Mar 2026 16:32:43 +0100
Subject: [PATCH 46/49] Bump actions/setup-java from 4 to 5 (#561)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)
---
updated-dependencies:
- dependency-name: actions/setup-java
dependency-version: '5'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Davide Melfi
---
.github/workflows/aws-lambda-java-core.yml | 2 +-
.github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +-
.github/workflows/aws-lambda-java-events.yml | 2 +-
.github/workflows/aws-lambda-java-log4j2.yml | 2 +-
.github/workflows/aws-lambda-java-profiler.yml | 2 +-
.github/workflows/aws-lambda-java-serialization.yml | 2 +-
.github/workflows/aws-lambda-java-tests.yml | 2 +-
.github/workflows/runtime-interface-client_merge_to_main.yml | 2 +-
.github/workflows/runtime-interface-client_pr.yml | 4 ++--
.github/workflows/samples.yml | 4 ++--
10 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml
index 373acf757..3e4364672 100644
--- a/.github/workflows/aws-lambda-java-core.yml
+++ b/.github/workflows/aws-lambda-java-core.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
index 713f97211..144d52f86 100644
--- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
+++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml
@@ -29,7 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml
index 0a8725339..18be63cf9 100644
--- a/.github/workflows/aws-lambda-java-events.yml
+++ b/.github/workflows/aws-lambda-java-events.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml
index 315678339..945a1cb30 100644
--- a/.github/workflows/aws-lambda-java-log4j2.yml
+++ b/.github/workflows/aws-lambda-java-log4j2.yml
@@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml
index 1007e7124..485d93110 100644
--- a/.github/workflows/aws-lambda-java-profiler.yml
+++ b/.github/workflows/aws-lambda-java-profiler.yml
@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@v6
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 21
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml
index 78d23c37b..ccc805be7 100644
--- a/.github/workflows/aws-lambda-java-serialization.yml
+++ b/.github/workflows/aws-lambda-java-serialization.yml
@@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml
index a52181dff..324c44514 100644
--- a/.github/workflows/aws-lambda-java-tests.yml
+++ b/.github/workflows/aws-lambda-java-tests.yml
@@ -30,7 +30,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index da2389d73..f66310755 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index 59b1c8aed..645f1069c 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
@@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index a76c3fb25..ef961c3e0 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -30,7 +30,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 8
distribution: corretto
@@ -54,7 +54,7 @@ jobs:
- uses: actions/checkout@v6
# Set up both Java 8 and 21
- name: Set up Java 8 and 21
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: |
8
From 3239ddac451d3678a8f248143560809645321ed7 Mon Sep 17 00:00:00 2001
From: Davide Melfi
Date: Tue, 31 Mar 2026 10:01:20 +0100
Subject: [PATCH 47/49] Re-apply Jackson 2.18.6 upgrade with round-trip test
coverage (#599)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* test: ๐งช add roundtrip serialization test utility
* test: fix false positives epoch format errors, added comment about this
in the serialization package.
* test: fixed false positves DateTime differences
* test: fixing error in lex event fixture
* test: fixing connect event
* test: fixing api gateway proxy event false negative
* test: fixing cloudfront and s3 event false negatives
* chore: adding mise to gitignore
* test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures
* test: Add round-trip fixtures for 4 registered events
Add JSON test fixtures and round-trip test cases for CognitoEvent,
DynamodbTimeWindowEvent, IoTButtonEvent, and KinesisTimeWindowEvent.
These were the only events registered in LambdaEventSerializers
SUPPORTED_EVENTS that lacked test fixtures.
Fixtures based on official AWS Lambda documentation examples. Time
window event fixtures use round-trip-safe date formats to avoid
coercion issues.
Coverage: 32 passing + 2 known failures (34 total, up from 30).
* test: Add round-trip fixtures for 21 unregistered events
Add UnregisteredEventSerializationRoundTripTest covering events not in
LambdaEventSerializers.SUPPORTED_EVENTS: 10 Cognito UserPool triggers,
5 Kinesis Analytics events, 2 API Gateway V2 WebSocket, 2 AppSync,
1 S3 Batch, and 1 TimeWindow response. S3ObjectLambdaEvent is a known
failure (Lombok xAmzRequestId naming issue).
Split SerializationRoundTripTest into registered (34 tests) and
unregistered (22 tests) for clarity. Total: 56 tests, 53 passing,
3 known failures.
* test: Add round-trip tests for 11 response event types
Add ResponseEventSerializationRoundTripTest covering all 11 response
event types in aws-lambda-java-events. 9 pass cleanly, 2 are known
failures (IamPolicyResponse, IamPolicyResponseV1 โ getPolicyDocument()
returns Map instead of the PolicyDocument POJO, making
round-trip impossible by design since these are serialize-only types).
Also update SerializationRoundTripTest comment for
APIGatewayV2CustomAuthorizerEvent to clarify the date format change
is a lossy transformation, not a bug.
Total: 69 tests (34 registered + 22 unregistered + 11 response + 2
LambdaEventAssertTest), all green. Coverage now 66/68 event classes
(97%).
* chore: fixed comment
* test: including IAM Policy Reponse roundtrip test
* test: add test for JsonNodeUtils
* chore: remove unwanted file on origin
* docs: add tests 1.1.2 changelog entry
* chore: remove entry in changelog
* fix: ๐ง updating again jacjson 2.15.4 -> 2.18.6
* fix: fixing an error in github actions that was causing false positive errors when running aws-lambda-java-serialization
* docs: update changelogs
* chore: releasing aws-lambda-tests
* chore: add space
---------
Co-authored-by: Davide Melfi
---
.../aws-lambda-java-serialization.yml | 2 +-
.../RELEASE.CHANGELOG.md | 7 +
aws-lambda-java-serialization/pom.xml | 4 +-
.../events/LambdaEventSerializers.java | 285 +++++++++---------
aws-lambda-java-tests/RELEASE.CHANGELOG.md | 6 +
aws-lambda-java-tests/pom.xml | 14 +-
.../runtime/tests/LambdaEventAssert.java | 7 +-
7 files changed, 164 insertions(+), 161 deletions(-)
diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml
index ccc805be7..f52c96fed 100644
--- a/.github/workflows/aws-lambda-java-serialization.yml
+++ b/.github/workflows/aws-lambda-java-serialization.yml
@@ -40,7 +40,7 @@ jobs:
# Package and install target module
- name: Package serialization with Maven
- run: mvn -B package install --file aws-lambda-java-serialization/pom.xml
+ run: mvn -B install --file aws-lambda-java-serialization/pom.xml
# Run tests
- name: Run tests from aws-lambda-java-tests
diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
index 4a0e8ca2a..d68d7b1fe 100644
--- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md
@@ -1,7 +1,14 @@
+### March 26, 2026
+`1.4.0`:
+- Update `jackson-databind` dependency from 2.15.4 to 2.18.6
+- Replace deprecated `PropertyNamingStrategy.PascalCaseStrategy` with `PropertyNamingStrategies.UpperCamelCaseStrategy`
+- The regression reported in 1.3.1 was a false positive caused by a CI workflow bug (`mvn package install` running the shade plugin twice, corrupting the artifact). Fixed by using `mvn install` instead.
+
### March 19, 2026
`1.3.1`:
- Revert `jackson-databind` dependency from 2.18.6 to 2.15.4
- Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy`
+- Note: reverted due to a suspected regression in Joda DateTime deserialization; later confirmed to be a false positive (see 1.4.0)
### March 11, 2026
`1.3.0`:
diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml
index a71eb1f8d..d412fd765 100644
--- a/aws-lambda-java-serialization/pom.xml
+++ b/aws-lambda-java-serialization/pom.xml
@@ -4,7 +4,7 @@
com.amazonaws
aws-lambda-java-serialization
- 1.3.1
+ 1.4.0
jar
AWS Lambda Java Runtime Serialization
@@ -32,7 +32,7 @@
1.8
1.8
com.amazonaws.lambda.thirdparty
- 2.15.4
+ 2.18.6
2.10.1
20231013
7.3.2
diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
index 3f01d99b1..533bdcd49 100644
--- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
+++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java
@@ -19,6 +19,7 @@
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil;
import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule;
import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule;
@@ -86,156 +87,146 @@ public class LambdaEventSerializers {
"com.amazonaws.services.lambda.runtime.events.SQSEvent")
.collect(Collectors.toList());
- /**
- * list of events incompatible with Jackson, with serializers explicitly defined
- * Classes are incompatible with Jackson for any of the following reasons:
- * 1. different constructor/setter types from getter types
- * 2. various bugs within Jackson
- */
- private static final Map SERIALIZER_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification",
- new S3EventSerializer<>()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification",
- new S3EventSerializer<>()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event",
- new S3EventSerializer<>()))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * list of events incompatible with Jackson, with serializers explicitly defined
+ * Classes are incompatible with Jackson for any of the following reasons:
+ * 1. different constructor/setter types from getter types
+ * 2. various bugs within Jackson
+ */
+ private static final Map SERIALIZER_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>()))
+ .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
- /**
- * Maps supported event classes to mixin classes with Jackson annotations.
- * Jackson annotations are not loaded through the ClassLoader so if a Java field
- * is serialized or deserialized from a
- * json field that does not match the Jave field name, then a Mixin is required.
- */
- @SuppressWarnings("rawtypes")
- private static final Map MIXIN_MAP = Stream.of(
- new SimpleEntry<>(
- "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
- CloudFormationCustomResourceEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
- CloudFrontEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
- CloudWatchLogsEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
- CodeCommitEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record",
- CodeCommitEventMixin.RecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
- ConnectEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details",
- ConnectEventMixin.DetailsMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData",
- ConnectEventMixin.ContactDataMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint",
- ConnectEventMixin.CustomerEndpointMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
- ConnectEventMixin.QueueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint",
- ConnectEventMixin.SystemEndpointMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
- DynamodbEventMixin.class),
- new SimpleEntry<>(
- "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
- DynamodbEventMixin.DynamodbStreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord",
- DynamodbEventMixin.StreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- DynamodbEventMixin.StreamRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue",
- DynamodbEventMixin.AttributeValueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- DynamodbEventMixin.AttributeValueMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
- DynamodbTimeWindowEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
- KinesisEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record",
- KinesisEventMixin.RecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
- KinesisTimeWindowEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
- ScheduledEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
- SecretsManagerRotationEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- SNSEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord",
- SNSEventMixin.SNSRecordMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
- SQSEventMixin.class),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage",
- SQSEventMixin.SQSMessageMixin.class))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * Maps supported event classes to mixin classes with Jackson annotations.
+ * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a
+ * json field that does not match the Jave field name, then a Mixin is required.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map MIXIN_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
+ CloudFormationCustomResourceEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent",
+ CloudFrontEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent",
+ CloudWatchLogsEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
+ CodeCommitEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record",
+ CodeCommitEventMixin.RecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
+ ConnectEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details",
+ ConnectEventMixin.DetailsMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData",
+ ConnectEventMixin.ContactDataMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint",
+ ConnectEventMixin.CustomerEndpointMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint",
+ ConnectEventMixin.SystemEndpointMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
+ DynamodbEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
+ DynamodbEventMixin.DynamodbStreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord",
+ DynamodbEventMixin.StreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ DynamodbEventMixin.StreamRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue",
+ DynamodbEventMixin.AttributeValueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ DynamodbEventMixin.AttributeValueMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
+ DynamodbTimeWindowEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
+ KinesisEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record",
+ KinesisEventMixin.RecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent",
+ KinesisTimeWindowEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent",
+ ScheduledEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent",
+ SecretsManagerRotationEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ SNSEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord",
+ SNSEventMixin.SNSRecordMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
+ SQSEventMixin.class),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage",
+ SQSEventMixin.SQSMessageMixin.class))
+ .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
- /**
- * If mixins are required for inner classes of an event, then those nested
- * classes must be specified here.
- */
- @SuppressWarnings("rawtypes")
- private static final Map> NESTED_CLASS_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
- new SimpleEntry<>(
- "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
- Arrays.asList(
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
- "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
- new AlternateNestedClass(
- "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
- "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
- new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
- Arrays.asList(
- new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage"))))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * If mixins are required for inner classes of an event, then those nested classes must be specified here.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map> NESTED_CLASS_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent",
+ Arrays.asList(
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue",
+ "com.amazonaws.services.dynamodbv2.model.AttributeValue"),
+ new AlternateNestedClass(
+ "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord",
+ "com.amazonaws.services.dynamodbv2.model.StreamRecord"),
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent",
+ Arrays.asList(
+ new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage"))))
+ .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
- /**
- * If event requires a naming strategy. For example, when someone names the
- * getter method getSNS and the setter
- * method setSns, for some magical reasons, using both mixins and a naming
- * strategy works
- */
- private static final Map NAMING_STRATEGY_MAP = Stream.of(
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
- new PropertyNamingStrategy.PascalCaseStrategy()),
- new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
- new PropertyNamingStrategy.PascalCaseStrategy()))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ /**
+ * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter
+ * method setSns, for some magical reasons, using both mixins and a naming strategy works
+ */
+ private static final Map NAMING_STRATEGY_MAP = Stream.of(
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent",
+ new PropertyNamingStrategies.UpperCamelCaseStrategy()),
+ new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue",
+ new PropertyNamingStrategies.UpperCamelCaseStrategy())
+ )
+ .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
/**
* Returns whether the class name is a Lambda supported event model.
diff --git a/aws-lambda-java-tests/RELEASE.CHANGELOG.md b/aws-lambda-java-tests/RELEASE.CHANGELOG.md
index 76965b8fd..0b4bd2510 100644
--- a/aws-lambda-java-tests/RELEASE.CHANGELOG.md
+++ b/aws-lambda-java-tests/RELEASE.CHANGELOG.md
@@ -1,3 +1,9 @@
+### March 27, 2026
+`1.1.3`:
+- Add serialization round-trip tests covering 66 event classes
+- Bumped `aws-lambda-java-serialization` to version `1.4.0` (Jackson `2.15.x` โ `2.18.6`)
+- Bumped `aws-lambda-java-events` to version `3.16.1`
+
### August 26, 2021
`1.1.1`:
- Bumped `aws-lambda-java-events` to version `3.11.0`
diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml
index 69a112cba..e63e529a2 100644
--- a/aws-lambda-java-tests/pom.xml
+++ b/aws-lambda-java-tests/pom.xml
@@ -5,7 +5,7 @@
com.amazonaws
aws-lambda-java-tests
- 1.1.2
+ 1.1.3
jar
AWS Lambda Java Tests
@@ -40,18 +40,22 @@
-->
5.9.2
0.8.7
+ 1.4.0
+ 3.16.1
+ 3.18.0
+ 3.27.7
com.amazonaws
aws-lambda-java-serialization
- 1.2.0
+ ${aws-lambda-java-serialization.version}
com.amazonaws
aws-lambda-java-events
- 3.16.1
+ ${aws-lambda-java-events.version}
org.junit.jupiter
@@ -71,13 +75,13 @@
org.apache.commons
commons-lang3
- 3.18.0
+ ${commons-lang3.version}
org.assertj
assertj-core
- 3.27.7
+ ${assertj-core.version}
test
diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
index e189b5e2b..f8d7e106d 100644
--- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
+++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java
@@ -25,13 +25,8 @@
* {@link AssertionError}.
*
*
- *
- * This class is intentionally package-private to support updates to
- * the aws-lambda-java-events and aws-lambda-java-serialization packages.
- * Consider making it public if there's a real request for it.
- *
*/
-class LambdaEventAssert {
+public class LambdaEventAssert {
private static final ObjectMapper MAPPER = new ObjectMapper();
From 7230727de124386f9e9e49c04f2586a384b1bb02 Mon Sep 17 00:00:00 2001
From: Maxime David
Date: Tue, 31 Mar 2026 13:40:49 -0400
Subject: [PATCH 48/49] fix: pin GitHub Actions (#601)
---
.github/workflows/repo-sync.yml | 4 ++--
.../runtime-interface-client_merge_to_main.yml | 12 ++++++------
.github/workflows/runtime-interface-client_pr.yml | 4 ++--
.github/workflows/samples.yml | 2 +-
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml
index 6a918fde3..4934754d8 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -22,7 +22,7 @@ jobs:
steps:
- uses: actions/checkout@v6
if: ${{ env.IS_CONFIGURED == 'true' }}
- - uses: repo-sync/github-sync@v2
+ - uses: repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88 # v2.3.0
name: Sync repo to branch
if: ${{ env.IS_CONFIGURED == 'true' }}
with:
@@ -30,7 +30,7 @@ jobs:
source_branch: main
destination_branch: ${{ secrets.INTERMEDIATE_BRANCH }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- - uses: repo-sync/pull-request@v2
+ - uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 # v2.12.1
name: Create pull request
if: ${{ env.IS_CONFIGURED == 'true' }}
with:
diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml
index f66310755..d0d479111 100644
--- a/.github/workflows/runtime-interface-client_merge_to_main.yml
+++ b/.github/workflows/runtime-interface-client_merge_to_main.yml
@@ -28,20 +28,20 @@ jobs:
contents: read
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: 8
distribution: corretto
cache: maven
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
install: true
@@ -62,7 +62,7 @@ jobs:
if: env.ENABLE_SNAPSHOT != null
env:
ENABLE_SNAPSHOT: ${{ secrets.ENABLE_SNAPSHOT }}
- uses: aws-actions/configure-aws-credentials@v4
+ uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_ROLE }}
@@ -91,6 +91,6 @@ jobs:
- name: Upload coverage to Codecov
if: env.CODECOV_TOKEN != null
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # 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 645f1069c..e0522005b 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -58,7 +58,7 @@ jobs:
cache: maven
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -90,6 +90,6 @@ jobs:
- name: Upload coverage to Codecov
if: env.CODECOV_TOKEN != null
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index ef961c3e0..68e25827d 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -73,7 +73,7 @@ jobs:
# Build custom-serialization samples
- name: install sam
- uses: aws-actions/setup-sam@v2
+ uses: aws-actions/setup-sam@d78e1a4a9656d3b223e59b80676a797f20093133 # v2
- name: test fastJson
run: cd samples/custom-serialization/fastJson && sam build && sam local invoke -e events/event.json | grep 200
- name: test gson
From c4dcbab4ffeda26ec9dff997d6ef02a9ee2ab013 Mon Sep 17 00:00:00 2001
From: Maxime David
Date: Wed, 1 Apr 2026 08:43:15 -0400
Subject: [PATCH 49/49] fix: pinning actions (#602)
---
.github/workflows/aws-lambda-java-profiler.yml | 8 ++++----
.github/workflows/runtime-interface-client_pr.yml | 12 ++++++------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml
index 485d93110..a098bfd14 100644
--- a/.github/workflows/aws-lambda-java-profiler.yml
+++ b/.github/workflows/aws-lambda-java-profiler.yml
@@ -22,17 +22,17 @@ jobs:
contents: read
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: 21
distribution: corretto
cache: maven
- name: Issue AWS credentials
- uses: aws-actions/configure-aws-credentials@v4
+ uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }}
role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }}
@@ -68,7 +68,7 @@ jobs:
run: ./integration_tests/download_from_s3.sh
- name: Upload profiles
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: profiles
path: /tmp/s3-artifacts
diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml
index e0522005b..a0d8c6cc8 100644
--- a/.github/workflows/runtime-interface-client_pr.yml
+++ b/.github/workflows/runtime-interface-client_pr.yml
@@ -22,10 +22,10 @@ jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: 8
distribution: corretto
@@ -48,10 +48,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up JDK 1.8
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: 8
distribution: corretto
@@ -61,7 +61,7 @@ jobs:
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
install: true
@@ -83,7 +83,7 @@ jobs:
IS_JAVA_8: true
- name: Save the built jar
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: aws-lambda-java-runtime-interface-client
path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar