-
Notifications
You must be signed in to change notification settings - Fork 1k
New serverless pattern - lambda-durable-execution-java-cdk #3074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e0e83df
8f0072a
abd1032
dc114d1
13d6593
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| node_modules | ||
| cdk.out | ||
| *.js | ||
| *.d.ts | ||
| package-lock.json | ||
| src/target |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| # Lambda Durable Execution with Java SDK | ||
|
|
||
| This pattern deploys a Lambda durable function written in Java that orchestrates a multi-step order processing workflow with automatic checkpointing and failure recovery. | ||
|
|
||
| Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-durable-execution-java-cdk | ||
|
|
||
| Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. | ||
| - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured | ||
| - [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||
| - [Node and NPM](https://nodejs.org/en/download/) installed | ||
| - [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed | ||
| - [Java 17+](https://docs.aws.amazon.com/corretto/latest/corretto-17-ug/downloads-list.html) installed | ||
| - [Apache Maven](https://maven.apache.org/install.html) installed | ||
|
|
||
| ## Deployment Instructions | ||
|
|
||
| 1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: | ||
| ```bash | ||
| git clone https://github.com/aws-samples/serverless-patterns | ||
| ``` | ||
| 2. Change directory to the pattern directory: | ||
| ```bash | ||
| cd serverless-patterns/lambda-durable-execution-java-cdk | ||
| ``` | ||
| 3. Build the Java Lambda function: | ||
| ```bash | ||
| cd src | ||
| mvn clean package -q | ||
| cd .. | ||
| ``` | ||
| 4. Install CDK dependencies: | ||
| ```bash | ||
| npm install | ||
| ``` | ||
| 5. Deploy the stack: | ||
| ```bash | ||
| cdk deploy | ||
| ``` | ||
| > **Note:** If this is the first time you deploy a CDK stack in this account/region, run `cdk bootstrap` before `cdk deploy`. | ||
|
|
||
| ## How it works | ||
|
|
||
| This pattern uses the [AWS Lambda Durable Execution SDK for Java](https://github.com/aws/aws-durable-execution-sdk-java/) (GA April 2026) to build a resilient order processing workflow. | ||
|
|
||
| The Java function extends `DurableHandler<Map, Map>` and uses `DurableContext` to: | ||
|
|
||
| 1. **Validate order** — `ctx.step()` checkpoints the validation result | ||
| 2. **Reserve inventory** — `ctx.step()` checkpoints the reservation ID | ||
| 3. **Process payment** — `ctx.step()` checkpoints the payment confirmation | ||
| 4. **Wait for warehouse** — `ctx.wait()` suspends execution for 5 seconds with zero compute charges | ||
| 5. **Confirm shipment** — `ctx.step()` checkpoints the tracking number | ||
|
|
||
| If the function is interrupted at any point, it replays from the beginning but skips completed steps using stored checkpoint results. | ||
|
|
||
| ### Architecture | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────┐ | ||
| │ Lambda Durable Function │ | ||
| │ │ | ||
| │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ | ||
| │ │ Validate │→ │ Reserve │→ │ Process │→ │ Wait │ │ | ||
| │ │ Order │ │Inventory │ │ Payment │ │ (free) │ │ | ||
| │ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ | ||
| │ ↓ checkpoint ↓ checkpoint ↓ checkpoint │ │ | ||
| │ ↓ │ | ||
| │ ┌──────────┐ │ | ||
| │ │ Confirm │ │ | ||
| │ │ Shipment │ │ | ||
| │ └──────────┘ │ | ||
| └─────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ### Key Java SDK concepts | ||
|
|
||
| - **`DurableHandler<I, O>`** — Base class for durable functions. Extend this and implement `handleRequest(I input, DurableContext ctx)`. | ||
| - **`ctx.step(name, type, fn)`** — Execute code with automatic checkpointing and retry support. | ||
| - **`ctx.wait(name, duration)`** — Suspend execution without compute charges. | ||
| - **`ctx.invoke()`** — Invoke another Lambda function and wait for the result. | ||
| - **`ctx.map()`** — Apply a function across a collection concurrently. | ||
| - **`ctx.parallel()`** — Run multiple operations concurrently. | ||
|
|
||
| ## Testing | ||
|
|
||
| 1. After deployment, note the `FunctionAliasArn` output. | ||
|
|
||
| 2. Invoke the durable function using the alias ARN: | ||
| ```bash | ||
| aws lambda invoke \ | ||
| --function-name <FunctionAliasArn> \ | ||
| --payload '{"orderId": "ORD-001", "amount": 149.99}' \ | ||
| --cli-binary-format raw-in-base64-out \ | ||
| output.json | ||
|
|
||
| cat output.json | ||
| ``` | ||
|
|
||
| 3. Expected output: | ||
| ```json | ||
| { | ||
| "orderId": "ORD-001", | ||
| "status": "COMPLETED", | ||
| "validation": "VALIDATED", | ||
| "reservationId": "RES-a1b2c3d4", | ||
| "paymentId": "PAY-e5f6g7h8", | ||
| "trackingNumber": "TRACK-i9j0k1l2" | ||
| } | ||
| ``` | ||
|
|
||
| 4. Monitor the durable execution in the Lambda console under the **Durable executions** tab. | ||
|
|
||
| ## Cleanup | ||
|
|
||
| ```bash | ||
| cdk destroy | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #!/usr/bin/env node | ||
| import "source-map-support/register"; | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import { LambdaDurableExecutionJavaStack } from "../lib/lambda-durable-execution-java-stack"; | ||
|
|
||
| const app = new cdk.App(); | ||
| new LambdaDurableExecutionJavaStack(app, "LambdaDurableExecutionJavaStack"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "app": "npx ts-node --prefer-ts-exts bin/app.ts", | ||
| "watch": { | ||
| "include": ["**"], | ||
| "exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "node_modules", "src"] | ||
| }, | ||
| "context": { | ||
| "@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
| "@aws-cdk/core:checkSecretUsage": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| { | ||
| "title": "Lambda Durable Execution with Java SDK", | ||
| "description": "Build a resilient, multi-step order processing workflow using the AWS Lambda Durable Execution SDK for Java with automatic checkpointing and failure recovery.", | ||
| "language": "Java", | ||
| "level": "300", | ||
| "framework": "AWS CDK", | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern deploys a Lambda durable function written in Java that orchestrates a multi-step order processing workflow. The function uses the Durable Execution SDK for Java (v1.0.1) to automatically checkpoint progress at each step.", | ||
| "The workflow: (1) validates the order, (2) reserves inventory, (3) processes payment, (4) waits for warehouse processing (no compute charges), (5) confirms shipment. Each step is checkpointed, so if the function is interrupted, it resumes from the last completed step.", | ||
| "This is the first Java-based durable execution pattern. The Java SDK provides an idiomatic experience with DurableHandler<I,O> and DurableContext, supporting steps, waits, callbacks, invoke, map, and parallel operations." | ||
| ] | ||
| }, | ||
| "gitHub": { | ||
| "template": { | ||
| "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-execution-java-cdk", | ||
| "templateURL": "serverless-patterns/lambda-durable-execution-java-cdk", | ||
| "projectFolder": "lambda-durable-execution-java-cdk", | ||
| "templateFile": "lib/lambda-durable-execution-java-stack.ts" | ||
| } | ||
| }, | ||
| "resources": { | ||
| "bullets": [ | ||
| { | ||
| "text": "Lambda Durable Execution SDK for Java on GitHub", | ||
| "link": "https://github.com/aws/aws-durable-execution-sdk-java/" | ||
| }, | ||
| { | ||
| "text": "Lambda Durable Functions Documentation", | ||
| "link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" | ||
| }, | ||
| { | ||
| "text": "Deploy Lambda durable functions with Infrastructure as Code", | ||
| "link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started-iac.html" | ||
| } | ||
| ] | ||
| }, | ||
| "deploy": { | ||
| "text": [ | ||
| "cd src && mvn clean package -q", | ||
| "cd .. && cdk deploy" | ||
| ] | ||
| }, | ||
| "testing": { | ||
| "text": [ | ||
| "See the GitHub repo for detailed testing instructions." | ||
| ] | ||
| }, | ||
| "cleanup": { | ||
| "text": [ | ||
| "Delete the stack: <code>cdk destroy</code>." | ||
| ] | ||
| }, | ||
| "authors": [ | ||
| { | ||
| "name": "Nithin Chandran R", | ||
| "bio": "Technical Account Manager at AWS", | ||
| "linkedin": "nithin-chandran-r" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import * as lambda from "aws-cdk-lib/aws-lambda"; | ||
| import * as iam from "aws-cdk-lib/aws-iam"; | ||
| import { Construct } from "constructs"; | ||
|
|
||
| export class LambdaDurableExecutionJavaStack extends cdk.Stack { | ||
| constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
| super(scope, id, props); | ||
|
|
||
| const fn = new lambda.Function(this, "DurableOrderProcessorFn", { | ||
| runtime: lambda.Runtime.JAVA_17, | ||
| handler: "com.example.OrderProcessor::handleRequest", | ||
| code: lambda.Code.fromAsset("src/target/lambda-durable-execution-java-1.0.0.jar"), | ||
| timeout: cdk.Duration.minutes(15), | ||
| memorySize: 512, | ||
| description: "Durable order processing workflow using Java SDK", | ||
| durableConfig: { | ||
| executionTimeout: cdk.Duration.hours(1), | ||
| retentionPeriod: cdk.Duration.days(7), | ||
| }, | ||
| }); | ||
|
|
||
| fn.role!.addManagedPolicy( | ||
| iam.ManagedPolicy.fromAwsManagedPolicyName( | ||
| "service-role/AWSLambdaBasicDurableExecutionRolePolicy" | ||
| ) | ||
| ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Native durableConfig property + L2 Alias is cleaner than the L1 escape hatch. The official CDK example (Deploy Lambda durable functions with IaC → AWS CDK) uses the native durableConfig property on lambda.Function and a regular lambda.Alias against fn.currentVersion: Two possible issues with the current escape-hatch approach:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great call. Upgraded to aws-cdk-lib@2.257.0 which has the native durableConfig property -- switched to that plus L2 Alias with fn.currentVersion. Much cleaner, no more escape hatches. The original approach was because 2.180.0 didn't have it yet. |
||
|
|
||
| const alias = new lambda.Alias(this, "ProdAlias", { | ||
| aliasName: "prod", | ||
| version: fn.currentVersion, | ||
| }); | ||
|
|
||
| new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName }); | ||
| new cdk.CfnOutput(this, "FunctionAliasArn", { | ||
| value: alias.aliasName, | ||
| description: "Use this ARN to invoke the durable function", | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "lambda-durable-execution-java-cdk", | ||
| "version": "1.0.0", | ||
| "bin": { | ||
| "app": "bin/app.ts" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "cdk": "cdk" | ||
| }, | ||
| "dependencies": { | ||
| "aws-cdk-lib": "^2.257.0", | ||
| "constructs": "10.4.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "ts-node": "^10.9.0", | ||
| "typescript": "~5.7.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package com.example; | ||
|
|
||
| import software.amazon.lambda.durable.DurableContext; | ||
| import software.amazon.lambda.durable.DurableHandler; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * Durable order processing workflow that demonstrates automatic checkpointing, | ||
| * wait operations, and multi-step orchestration using the Java Durable Execution SDK. | ||
| * | ||
| * Each step is checkpointed — if the function is interrupted, it resumes from | ||
| * the last completed step without re-executing previous work. | ||
| */ | ||
| public class OrderProcessor extends DurableHandler<Map<String, Object>, Map<String, Object>> { | ||
|
|
||
| @Override | ||
| public Map<String, Object> handleRequest(Map<String, Object> input, DurableContext ctx) { | ||
| // Wrap UUID generation in a step to ensure determinism on replay | ||
| String orderId = input.containsKey("orderId") | ||
| ? (String) input.get("orderId") | ||
| : ctx.step("generate-order-id", String.class, stepCtx -> UUID.randomUUID().toString()); | ||
| double amount = ((Number) input.getOrDefault("amount", 99.99)).doubleValue(); | ||
|
|
||
| // Step 1: Validate order | ||
| String validation = ctx.step("validate-order", String.class, stepCtx -> { | ||
| stepCtx.getLogger().info("Validating order " + orderId); | ||
| if (amount <= 0) { | ||
| throw new IllegalArgumentException("Invalid order amount: " + amount); | ||
| } | ||
| return "VALIDATED"; | ||
| }); | ||
|
|
||
| // Step 2: Reserve inventory | ||
| String reservationId = ctx.step("reserve-inventory", String.class, stepCtx -> { | ||
| stepCtx.getLogger().info("Reserving inventory for order " + orderId); | ||
| return "RES-" + UUID.randomUUID().toString().substring(0, 8); | ||
| }); | ||
|
|
||
| // Step 3: Process payment | ||
| String paymentId = ctx.step("process-payment", String.class, stepCtx -> { | ||
| stepCtx.getLogger().info("Processing payment of $" + amount + " for order " + orderId); | ||
| return "PAY-" + UUID.randomUUID().toString().substring(0, 8); | ||
| }); | ||
|
|
||
| // Wait for warehouse processing (no compute charges during wait) | ||
| ctx.wait("warehouse-processing", Duration.ofSeconds(5)); | ||
|
|
||
| // Step 4: Confirm shipment | ||
| String trackingNumber = ctx.step("confirm-shipment", String.class, stepCtx -> { | ||
| stepCtx.getLogger().info("Confirming shipment for order " + orderId); | ||
| return "TRACK-" + UUID.randomUUID().toString().substring(0, 8); | ||
| }); | ||
|
|
||
| return Map.of( | ||
| "orderId", orderId, | ||
| "status", "COMPLETED", | ||
| "validation", validation, | ||
| "reservationId", reservationId, | ||
| "paymentId", paymentId, | ||
| "trackingNumber", trackingNumber | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <groupId>com.example</groupId> | ||
| <artifactId>lambda-durable-execution-java</artifactId> | ||
| <version>1.0.0</version> | ||
| <packaging>jar</packaging> | ||
|
|
||
| <properties> | ||
| <maven.compiler.source>17</maven.compiler.source> | ||
| <maven.compiler.target>17</maven.compiler.target> | ||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| </properties> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>software.amazon.lambda.durable</groupId> | ||
| <artifactId>aws-durable-execution-sdk-java</artifactId> | ||
| <version>1.0.1</version> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-shade-plugin</artifactId> | ||
| <version>3.6.0</version> | ||
| <executions> | ||
| <execution> | ||
| <phase>package</phase> | ||
| <goals> | ||
| <goal>shade</goal> | ||
| </goals> | ||
| </execution> | ||
| </executions> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a one-line note for first-time CDK users:
e.g. If this is the first time you deploy a CDK stack in this account/region, run cdk bootstrap before cdk deploy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added:
> **Note:** If this is the first time you deploy a CDK stack in this account/region, run \cdk bootstrap` before `cdk deploy`.`