diff --git a/docs/sdk-reference/languages/java/index.md b/docs/sdk-reference/languages/java/index.md index eaeef9e..603ec6c 100644 --- a/docs/sdk-reference/languages/java/index.md +++ b/docs/sdk-reference/languages/java/index.md @@ -1,3 +1,294 @@ # Java SDK -Coming soon. +The Java SDK (`aws-durable-execution-sdk-java`) runs in your Lambda functions and +provides `DurableHandler`, `DurableContext`, durable operations, configurable +serialization, and local/cloud testing utilities. + +The SDK supports Java 17 and newer. Virtual threads require Java 21. + +## Installation + +Add the execution SDK to the Lambda function package. Replace the version with the +latest release from +[Maven Central](https://central.sonatype.com/artifact/software.amazon.lambda.durable/aws-durable-execution-sdk-java). + +```xml + + software.amazon.lambda.durable + aws-durable-execution-sdk-java + 1.2.1 + + + com.amazonaws + aws-lambda-java-core + 1.4.0 + +``` + +For local and cloud tests, add the testing SDK in test scope: + +```xml + + software.amazon.lambda.durable + aws-durable-execution-sdk-java-testing + 1.2.1 + test + +``` + +Package the Lambda as a shaded jar so the SDK and its dependencies are available at +runtime: + +```xml + + org.apache.maven.plugins + maven-shade-plugin + 3.6.2 + + false + + + + package + shade + + + +``` + +## Usage + +Extend `DurableHandler` and implement `handleRequest(I input, DurableContext ctx)`. +Use the context to create steps, waits, callbacks, child contexts, invokes, maps, and +parallel branches. + +```java +import java.time.Duration; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +public class OrderProcessor extends DurableHandler { + + @Override + public OrderResult handleRequest(Order order, DurableContext ctx) { + var reservation = ctx.step("reserve-inventory", Reservation.class, + stepCtx -> inventoryService.reserve(order.items())); + + ctx.wait("warehouse-delay", Duration.ofHours(2)); + + var shipment = ctx.step("ship-order", Shipment.class, + stepCtx -> shippingService.ship(reservation, order.address())); + + return new OrderResult(order.id(), shipment.trackingNumber()); + } +} +``` + +Operation names are required in Java. Use stable, descriptive names so replay can match +new execution to checkpointed state. + +## Configuration + +Override `createConfiguration()` to customize serialization, the Lambda client, logging, +polling, checkpoint batching, plugins, or the executor used for user-defined async work. + +```java +import java.time.Duration; +import java.util.concurrent.Executors; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +public class ConfiguredHandler extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + var lambdaClientBuilder = LambdaClient.builder() + .region(Region.US_WEST_2); + + return DurableConfig.builder() + .withLambdaClientBuilder(lambdaClientBuilder) + .withExecutorService(Executors.newFixedThreadPool(16)) + .withCheckpointDelay(Duration.ofMillis(10)) + .build(); + } + + @Override + public Output handleRequest(Input input, DurableContext ctx) { + // Durable function logic + } +} +``` + +The configured executor is used for user operations such as async steps and concurrent +branches. Internal SDK polling and checkpoint coordination use SDK-managed threads. + +## 2.x Upgrade + +When upgrading from `1.x` to `2.x`, review the Java SDK migration guide in the +[SDK repository](https://github.com/aws/aws-durable-execution-sdk-java/blob/main/docs/migration-1.x-to-2.x.md). +The main changes are: + +- Replace `StepConfig.builder().semantics(...)` with `semanticsPerRetry(...)`. +- Preserve old `AT_MOST_ONCE_PER_RETRY` behavior by also setting + `retryStrategy(RetryStrategies.Presets.NO_RETRY)`. +- Update log queries and dashboards from `durableExecutionArn`, `contextId`, and + `contextName` to `executionArn`, `operationId`, and `operationName`. +- Move replay checks to `DurableContext.isReplaying()`; `StepContext` no longer exposes + replay state. +- Update tests and error handling that expect invalid context usage to throw + `IllegalDurableOperationException`; `2.x` throws `IllegalStateException`. +- Verify custom `SerDes` implementations can deserialize values immediately after + serialization. `2.x` validates this round trip before checkpointing by default. + +Useful searches before upgrading: + +```console +rg -n "\.semantics\(" . +rg -n "durableExecutionArn|contextId|contextName" . +rg -n "isReplaying|StepContext" . +``` + +If you need a temporary log compatibility window, configure old MDC key names while +dashboards are migrated: + +```java +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.logging.LoggerConfig; + +@Override +protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withLoggerConfig(new LoggerConfig(true, true)) + .build(); +} +``` + +## FAQ + +### Can I use a virtual thread pool? + +Yes. The SDK baseline is Java 17, so it does not use virtual threads by default. On +Java 21 or newer, provide a virtual-thread executor through `DurableConfig`. + +```java +import java.util.concurrent.Executors; +import software.amazon.lambda.durable.DurableConfig; + +@Override +protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withExecutorService(Executors.newVirtualThreadPerTaskExecutor()) + .build(); +} +``` + +This executor only runs user-defined operation code. Virtual threads can help when you +create many concurrent async steps or branches, but they do not remove Lambda memory, +timeout, downstream service, or durable execution service limits. + +### Can a Java durable function be triggered by SQS, SNS, EventBridge, or other event sources? + +Yes, but Java event model deserialization needs care. The default user-data serializer is +the SDK's Jackson-based `JacksonSerDes`, not the same serializer the Lambda Java runtime +uses for event classes from `aws-lambda-java-events`. For broad event-source support, add +the Lambda Java serialization library, which provides the +`com.amazonaws.services.lambda.runtime.serialization` package, and route Lambda event +classes through it. + +```xml + + com.amazonaws + aws-lambda-java-serialization + VERSION + +``` + +Then provide a `SerDes` adapter: + +```java +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; +import java.lang.reflect.Type; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.TypeToken; +import software.amazon.lambda.durable.serde.JacksonSerDes; +import software.amazon.lambda.durable.serde.SerDes; + +@Override +protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withSerDes(new LambdaEventSerDes()) + .build(); +} + +final class LambdaEventSerDes implements SerDes { + private final SerDes fallback = new JacksonSerDes(); + private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + @Override + public String serialize(Object value) { + return fallback.serialize(value); + } + + @Override + public T deserialize(String data, TypeToken typeToken) { + Type type = typeToken.getType(); + if (type instanceof Class clazz && isLambdaEvent(clazz)) { + @SuppressWarnings("unchecked") + var serializer = LambdaEventSerializers.serializerFor((Class) clazz, classLoader); + return serializer.fromJson(data); + } + + return fallback.deserialize(data, typeToken); + } + + private static boolean isLambdaEvent(Class clazz) { + return clazz.getName().startsWith("com.amazonaws.services.lambda.runtime.events."); + } +} +``` + +This pattern addresses the event-trigger deserialization problem discussed in +[aws/aws-durable-execution-sdk-java#366](https://github.com/aws/aws-durable-execution-sdk-java/issues/366). +The configured `SerDes` is also used as the default for steps, child contexts, callbacks, +and other operations unless an operation-specific config provides its own serializer. + +### Can I use Lambda SnapStart? + +Yes, but avoid the Java SDK default Lambda client if SnapStart restores a snapshot after +the environment-variable credentials captured at initialization have expired. The +default client uses `EnvironmentVariableCredentialsProvider`, which can cause +authentication failures after restore. + +For SnapStart, provide your own `LambdaClient` with a credentials provider that refreshes +after restore, then pass it to `withLambdaClientBuilder(...)`: + +```java +import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.lambda.LambdaClient; +import software.amazon.lambda.durable.DurableConfig; + +@Override +protected DurableConfig createConfiguration() { + var lambdaClientBuilder = LambdaClient.builder() + .region(Region.of(System.getenv("AWS_REGION"))) + .credentialsProvider(ContainerCredentialsProvider.builder().build()); + + return DurableConfig.builder() + .withLambdaClientBuilder(lambdaClientBuilder) + .build(); +} +``` + +If you customize the Lambda client for SnapStart, keep the region explicit and avoid +caching resolved credentials yourself during initialization. + +### Where is the Java SDK source and API reference? + +The source is in +[aws/aws-durable-execution-sdk-java](https://github.com/aws/aws-durable-execution-sdk-java). +The generated Javadoc is published at +[aws.github.io/aws-durable-execution-sdk-java/javadoc](https://aws.github.io/aws-durable-execution-sdk-java/javadoc/).