|
1 | 1 | # Java SDK |
2 | 2 |
|
3 | | -Coming soon. |
| 3 | +The Java SDK (`aws-durable-execution-sdk-java`) runs in your Lambda functions and |
| 4 | +provides `DurableHandler`, `DurableContext`, durable operations, configurable |
| 5 | +serialization, and local/cloud testing utilities. |
| 6 | + |
| 7 | +The SDK supports Java 17 and newer. Virtual threads require Java 21. |
| 8 | + |
| 9 | +## Installation |
| 10 | + |
| 11 | +Add the execution SDK to the Lambda function package. Replace the version with the |
| 12 | +latest release from |
| 13 | +[Maven Central](https://central.sonatype.com/artifact/software.amazon.lambda.durable/aws-durable-execution-sdk-java). |
| 14 | + |
| 15 | +```xml |
| 16 | +<dependency> |
| 17 | + <groupId>software.amazon.lambda.durable</groupId> |
| 18 | + <artifactId>aws-durable-execution-sdk-java</artifactId> |
| 19 | + <version>1.2.1</version> |
| 20 | +</dependency> |
| 21 | +<dependency> |
| 22 | + <groupId>com.amazonaws</groupId> |
| 23 | + <artifactId>aws-lambda-java-core</artifactId> |
| 24 | + <version>1.4.0</version> |
| 25 | +</dependency> |
| 26 | +``` |
| 27 | + |
| 28 | +For local and cloud tests, add the testing SDK in test scope: |
| 29 | + |
| 30 | +```xml |
| 31 | +<dependency> |
| 32 | + <groupId>software.amazon.lambda.durable</groupId> |
| 33 | + <artifactId>aws-durable-execution-sdk-java-testing</artifactId> |
| 34 | + <version>1.2.1</version> |
| 35 | + <scope>test</scope> |
| 36 | +</dependency> |
| 37 | +``` |
| 38 | + |
| 39 | +Package the Lambda as a shaded jar so the SDK and its dependencies are available at |
| 40 | +runtime: |
| 41 | + |
| 42 | +```xml |
| 43 | +<plugin> |
| 44 | + <groupId>org.apache.maven.plugins</groupId> |
| 45 | + <artifactId>maven-shade-plugin</artifactId> |
| 46 | + <version>3.6.2</version> |
| 47 | + <configuration> |
| 48 | + <createDependencyReducedPom>false</createDependencyReducedPom> |
| 49 | + </configuration> |
| 50 | + <executions> |
| 51 | + <execution> |
| 52 | + <phase>package</phase> |
| 53 | + <goals><goal>shade</goal></goals> |
| 54 | + </execution> |
| 55 | + </executions> |
| 56 | +</plugin> |
| 57 | +``` |
| 58 | + |
| 59 | +## Usage |
| 60 | + |
| 61 | +Extend `DurableHandler<I, O>` and implement `handleRequest(I input, DurableContext ctx)`. |
| 62 | +Use the context to create steps, waits, callbacks, child contexts, invokes, maps, and |
| 63 | +parallel branches. |
| 64 | + |
| 65 | +```java |
| 66 | +import java.time.Duration; |
| 67 | +import software.amazon.lambda.durable.DurableContext; |
| 68 | +import software.amazon.lambda.durable.DurableHandler; |
| 69 | + |
| 70 | +public class OrderProcessor extends DurableHandler<Order, OrderResult> { |
| 71 | + |
| 72 | + @Override |
| 73 | + public OrderResult handleRequest(Order order, DurableContext ctx) { |
| 74 | + var reservation = ctx.step("reserve-inventory", Reservation.class, |
| 75 | + stepCtx -> inventoryService.reserve(order.items())); |
| 76 | + |
| 77 | + ctx.wait("warehouse-delay", Duration.ofHours(2)); |
| 78 | + |
| 79 | + var shipment = ctx.step("ship-order", Shipment.class, |
| 80 | + stepCtx -> shippingService.ship(reservation, order.address())); |
| 81 | + |
| 82 | + return new OrderResult(order.id(), shipment.trackingNumber()); |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +Operation names are required in Java. Use stable, descriptive names so replay can match |
| 88 | +new execution to checkpointed state. |
| 89 | + |
| 90 | +## Configuration |
| 91 | + |
| 92 | +Override `createConfiguration()` to customize serialization, the Lambda client, logging, |
| 93 | +polling, checkpoint batching, plugins, or the executor used for user-defined async work. |
| 94 | + |
| 95 | +```java |
| 96 | +import java.time.Duration; |
| 97 | +import java.util.concurrent.Executors; |
| 98 | +import software.amazon.awssdk.regions.Region; |
| 99 | +import software.amazon.awssdk.services.lambda.LambdaClient; |
| 100 | +import software.amazon.lambda.durable.DurableConfig; |
| 101 | +import software.amazon.lambda.durable.DurableContext; |
| 102 | +import software.amazon.lambda.durable.DurableHandler; |
| 103 | + |
| 104 | +public class ConfiguredHandler extends DurableHandler<Input, Output> { |
| 105 | + |
| 106 | + @Override |
| 107 | + protected DurableConfig createConfiguration() { |
| 108 | + var lambdaClientBuilder = LambdaClient.builder() |
| 109 | + .region(Region.US_WEST_2); |
| 110 | + |
| 111 | + return DurableConfig.builder() |
| 112 | + .withLambdaClientBuilder(lambdaClientBuilder) |
| 113 | + .withExecutorService(Executors.newFixedThreadPool(16)) |
| 114 | + .withCheckpointDelay(Duration.ofMillis(10)) |
| 115 | + .build(); |
| 116 | + } |
| 117 | + |
| 118 | + @Override |
| 119 | + public Output handleRequest(Input input, DurableContext ctx) { |
| 120 | + // Durable function logic |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +The configured executor is used for user operations such as async steps and concurrent |
| 126 | +branches. Internal SDK polling and checkpoint coordination use SDK-managed threads. |
| 127 | + |
| 128 | +## 2.x Upgrade |
| 129 | + |
| 130 | +When upgrading from `1.x` to `2.x`, review the Java SDK migration guide in the |
| 131 | +[SDK repository](https://github.com/aws/aws-durable-execution-sdk-java/blob/main/docs/migration-1.x-to-2.x.md). |
| 132 | +The main changes are: |
| 133 | + |
| 134 | +- Replace `StepConfig.builder().semantics(...)` with `semanticsPerRetry(...)`. |
| 135 | +- Preserve old `AT_MOST_ONCE_PER_RETRY` behavior by also setting |
| 136 | + `retryStrategy(RetryStrategies.Presets.NO_RETRY)`. |
| 137 | +- Update log queries and dashboards from `durableExecutionArn`, `contextId`, and |
| 138 | + `contextName` to `executionArn`, `operationId`, and `operationName`. |
| 139 | +- Move replay checks to `DurableContext.isReplaying()`; `StepContext` no longer exposes |
| 140 | + replay state. |
| 141 | +- Update tests and error handling that expect invalid context usage to throw |
| 142 | + `IllegalDurableOperationException`; `2.x` throws `IllegalStateException`. |
| 143 | +- Verify custom `SerDes` implementations can deserialize values immediately after |
| 144 | + serialization. `2.x` validates this round trip before checkpointing by default. |
| 145 | + |
| 146 | +Useful searches before upgrading: |
| 147 | + |
| 148 | +```console |
| 149 | +rg -n "\.semantics\(" . |
| 150 | +rg -n "durableExecutionArn|contextId|contextName" . |
| 151 | +rg -n "isReplaying|StepContext" . |
| 152 | +``` |
| 153 | + |
| 154 | +If you need a temporary log compatibility window, configure old MDC key names while |
| 155 | +dashboards are migrated: |
| 156 | + |
| 157 | +```java |
| 158 | +import software.amazon.lambda.durable.DurableConfig; |
| 159 | +import software.amazon.lambda.durable.logging.LoggerConfig; |
| 160 | + |
| 161 | +@Override |
| 162 | +protected DurableConfig createConfiguration() { |
| 163 | + return DurableConfig.builder() |
| 164 | + .withLoggerConfig(new LoggerConfig(true, true)) |
| 165 | + .build(); |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +## FAQ |
| 170 | + |
| 171 | +### Can I use a virtual thread pool? |
| 172 | + |
| 173 | +Yes. The SDK baseline is Java 17, so it does not use virtual threads by default. On |
| 174 | +Java 21 or newer, provide a virtual-thread executor through `DurableConfig`. |
| 175 | + |
| 176 | +```java |
| 177 | +import java.util.concurrent.Executors; |
| 178 | +import software.amazon.lambda.durable.DurableConfig; |
| 179 | + |
| 180 | +@Override |
| 181 | +protected DurableConfig createConfiguration() { |
| 182 | + return DurableConfig.builder() |
| 183 | + .withExecutorService(Executors.newVirtualThreadPerTaskExecutor()) |
| 184 | + .build(); |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +This executor only runs user-defined operation code. Virtual threads can help when you |
| 189 | +create many concurrent async steps or branches, but they do not remove Lambda memory, |
| 190 | +timeout, downstream service, or durable execution service limits. |
| 191 | + |
| 192 | +### Can a Java durable function be triggered by SQS, SNS, EventBridge, or other event sources? |
| 193 | + |
| 194 | +Yes, but Java event model deserialization needs care. The default user-data serializer is |
| 195 | +the SDK's Jackson-based `JacksonSerDes`, not the same serializer the Lambda Java runtime |
| 196 | +uses for event classes from `aws-lambda-java-events`. For broad event-source support, add |
| 197 | +the Lambda Java serialization library, which provides the |
| 198 | +`com.amazonaws.services.lambda.runtime.serialization` package, and route Lambda event |
| 199 | +classes through it. |
| 200 | + |
| 201 | +```xml |
| 202 | +<dependency> |
| 203 | + <groupId>com.amazonaws</groupId> |
| 204 | + <artifactId>aws-lambda-java-serialization</artifactId> |
| 205 | + <version>VERSION</version> |
| 206 | +</dependency> |
| 207 | +``` |
| 208 | + |
| 209 | +Then provide a `SerDes` adapter: |
| 210 | + |
| 211 | +```java |
| 212 | +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; |
| 213 | +import java.lang.reflect.Type; |
| 214 | +import software.amazon.lambda.durable.DurableConfig; |
| 215 | +import software.amazon.lambda.durable.TypeToken; |
| 216 | +import software.amazon.lambda.durable.serde.JacksonSerDes; |
| 217 | +import software.amazon.lambda.durable.serde.SerDes; |
| 218 | + |
| 219 | +@Override |
| 220 | +protected DurableConfig createConfiguration() { |
| 221 | + return DurableConfig.builder() |
| 222 | + .withSerDes(new LambdaEventSerDes()) |
| 223 | + .build(); |
| 224 | +} |
| 225 | + |
| 226 | +final class LambdaEventSerDes implements SerDes { |
| 227 | + private final SerDes fallback = new JacksonSerDes(); |
| 228 | + private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| 229 | + |
| 230 | + @Override |
| 231 | + public String serialize(Object value) { |
| 232 | + return fallback.serialize(value); |
| 233 | + } |
| 234 | + |
| 235 | + @Override |
| 236 | + public <T> T deserialize(String data, TypeToken<T> typeToken) { |
| 237 | + Type type = typeToken.getType(); |
| 238 | + if (type instanceof Class<?> clazz && isLambdaEvent(clazz)) { |
| 239 | + @SuppressWarnings("unchecked") |
| 240 | + var serializer = LambdaEventSerializers.serializerFor((Class<T>) clazz, classLoader); |
| 241 | + return serializer.fromJson(data); |
| 242 | + } |
| 243 | + |
| 244 | + return fallback.deserialize(data, typeToken); |
| 245 | + } |
| 246 | + |
| 247 | + private static boolean isLambdaEvent(Class<?> clazz) { |
| 248 | + return clazz.getName().startsWith("com.amazonaws.services.lambda.runtime.events."); |
| 249 | + } |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +This pattern addresses the event-trigger deserialization problem discussed in |
| 254 | +[aws/aws-durable-execution-sdk-java#366](https://github.com/aws/aws-durable-execution-sdk-java/issues/366). |
| 255 | +The configured `SerDes` is also used as the default for steps, child contexts, callbacks, |
| 256 | +and other operations unless an operation-specific config provides its own serializer. |
| 257 | + |
| 258 | +### Can I use Lambda SnapStart? |
| 259 | + |
| 260 | +Yes, but avoid the Java SDK default Lambda client if SnapStart restores a snapshot after |
| 261 | +the environment-variable credentials captured at initialization have expired. The |
| 262 | +default client uses `EnvironmentVariableCredentialsProvider`, which can cause |
| 263 | +authentication failures after restore. |
| 264 | + |
| 265 | +For SnapStart, provide your own `LambdaClient` with a credentials provider that refreshes |
| 266 | +after restore, then pass it to `withLambdaClientBuilder(...)`: |
| 267 | + |
| 268 | +```java |
| 269 | +import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider; |
| 270 | +import software.amazon.awssdk.regions.Region; |
| 271 | +import software.amazon.awssdk.services.lambda.LambdaClient; |
| 272 | +import software.amazon.lambda.durable.DurableConfig; |
| 273 | + |
| 274 | +@Override |
| 275 | +protected DurableConfig createConfiguration() { |
| 276 | + var lambdaClientBuilder = LambdaClient.builder() |
| 277 | + .region(Region.of(System.getenv("AWS_REGION"))) |
| 278 | + .credentialsProvider(ContainerCredentialsProvider.builder().build()); |
| 279 | + |
| 280 | + return DurableConfig.builder() |
| 281 | + .withLambdaClientBuilder(lambdaClientBuilder) |
| 282 | + .build(); |
| 283 | +} |
| 284 | +``` |
| 285 | + |
| 286 | +If you customize the Lambda client for SnapStart, keep the region explicit and avoid |
| 287 | +caching resolved credentials yourself during initialization. |
| 288 | + |
| 289 | +### Where is the Java SDK source and API reference? |
| 290 | + |
| 291 | +The source is in |
| 292 | +[aws/aws-durable-execution-sdk-java](https://github.com/aws/aws-durable-execution-sdk-java). |
| 293 | +The generated Javadoc is published at |
| 294 | +[aws.github.io/aws-durable-execution-sdk-java/javadoc](https://aws.github.io/aws-durable-execution-sdk-java/javadoc/). |
0 commit comments