Skip to content

Added Java SDK support to the AWS Lambda durable functions skill#112

Open
smoell wants to merge 4 commits intoawslabs:mainfrom
smoell:main
Open

Added Java SDK support to the AWS Lambda durable functions skill#112
smoell wants to merge 4 commits intoawslabs:mainfrom
smoell:main

Conversation

@smoell
Copy link
Copy Markdown

@smoell smoell commented Apr 1, 2026

Add comprehensive Java examples to all reference documentation sections

Related

Ensures complete language coverage for the Lambda Durable Functions skill across TypeScript, Python, and
Java, enabling developers to use the skill regardless of their runtime preference.

Changes

Documentation Updates:

Added missing Python and Java examples to 17 sections across 4 reference files:

  • concurrent-operations.md (10 sections):

    • Tolerated Failures, Check Status, Get Results, Error Handling
    • Fixed Concurrency, Dynamic Concurrency
    • Map with Callbacks, Nested Map Operations, Map with Child Contexts
    • Performance Optimization (Batch Size Selection and Early Termination)
  • wait-operations.md (4 sections):

    • Custom Wait Strategy
    • Human Approval Workflow, Webhook Integration, Async Job Polling
  • advanced-patterns.md (2 sections):

    • AtMostOncePerRetry vs AtLeastOncePerRetry (Step Semantics)
    • Complex Object Graphs (Custom Serialization)
  • getting-started.md (1 section):

    • Saga Pattern (Compensating Transactions)

Key Patterns Documented:

  • Python: MapConfig, CompletionConfig, WaitForConditionConfig, StepSemantics, custom SerDes
    implementations
  • Java: Builder patterns for MapConfig, CompletionConfig, WaitForConditionConfig, WaitStrategies,
    custom SerDes<T> interface, DurableFuture async operations

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this
contribution, under the terms of the project
license
.

@smoell smoell requested review from a team as code owners April 1, 2026 15:25
**Java:**

```java
// Use DurableFuture.allOf() for parallel heterogeneous operations
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Updated concurrent-operations.md lines 138-167
  • Added native ctx.parallel() implementation using ParallelDurableFuture (verified from SDK source)
  • Implemented correct pattern based on actual SDK API:
    • Call ctx.parallel(name, ParallelConfig) returns ParallelDurableFuture
    • Use try-with-resources (implements AutoCloseable)
    • Call parallel.branch(name, type, func) for each branch - returns DurableFuture<T>
    • Branches complete automatically when try block exits (calls close() which calls get())
    • Access individual results with branchFuture.get()

s -> shippingService.createShipment(event.getAddress(), event.getItems()));

return new OrderResult(true, shipment.getOrderId());
} catch (Exception e) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also catch SuspendExecutionException, which doesn't mean Order failed

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Changed catch (Exception e) to catch (DurableExecutionException e) in error-handling.md lines 305-316
  • Added import for DurableExecutionException
  • Added explanatory comment: "Catch only business logic exceptions - not SuspendExecutionException. SuspendExecutionException is used internally for checkpointing and should propagate."
  • Updated inner catch block to also use DurableExecutionException

- **Steps**: `ctx.step("name", ResultType.class, stepCtx -> operation())` - type must be specified
- **Wait**: `ctx.wait("name", Duration.ofSeconds(n))` - always name waits for debugging
- **Generic Types**: Use `TypeToken` for generic types like `List<T>`: `ctx.step("name", new TypeToken<List<User>>() {}, stepCtx -> ...)`
- **Exceptions**: `StepFailedException`, `StepInterruptedException`, `CallbackTimeoutException`, `CallbackFailedException`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a complete list. Will agents be able to discover them all with this hint or we have to list all the operations and exceptions here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed Java exception types in SKILL.md

**Implementation approach:**

1. Use `waitForCallback` (TypeScript) or `wait_for_callback` (Python) with a timeout configuration set in the config argument
1. Use `waitForCallback` (TypeScript), `wait_for_callback` (Python), or `ctx.waitForCallback` (Java) with a timeout configuration set in the config argument
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the java reference include the ctx object while the others don't?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

In TypeScript, native setTimeout (and patterns like Promise.race using it) will fail during execution replays. To create a reliable timeout that persists across execution (expands over multi invocations), always use the timeout parameter provided by waitForCallback or waitForCondition.

**Java considerations:**
Java does not have an equivalent to Promise.race for local timeouts within a single invocation. Always use the timeout configuration in `CallbackConfig` or `WaitForConditionConfig` for reliable cross-invocation timeouts.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always use the timeout configuration in CallbackConfigorWaitForConditionConfig for reliable cross-invocation timeouts. -> is this specifically a Java consideration?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java does not have an equivalent to Promise.race

Java does have something equivalent: DurableFuture.anyOf

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Added DurableFuture.anyOf() documentation to advanced-error-handling.md
  • Added new "Java equivalent - DurableFuture.anyOf" section after the TypeScript Promise.race section


**Exception types by language:**

- **TypeScript**: Timeout errors thrown from `waitForCallback` or `waitForCondition`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this include the error types like the others?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


- **TypeScript**: Timeout errors thrown from `waitForCallback` or `waitForCondition`
- **Python**: `CallbackError` for callback failures
- **Java**: `CallbackTimeoutException`, `CallbackFailedException`, `WaitForConditionFailedException`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have categorization of the exception types?

**Exception types by language:**

- **TypeScript**: Timeout errors thrown from `waitForCallback` or `waitForCondition`
- **Python**: `CallbackError` for callback failures
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't there more?

│ └── com/example/
│ └── MyHandlerTest.java # Tests with DurableFunctionTestRunner
├── infrastructure/
│ └── template.yaml # SAM/CloudFormation
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be encouraging CDK?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


```xml
<properties>
<aws-durable-execution-sdk-java.version>LATEST</aws-durable-execution-sdk-java.version>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will expose customers to major version bumps and release candidates, won't it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


### Java

- [ ] Add SDK dependencies to `pom.xml` with version property set to `LATEST`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


- [ ] Add SDK dependencies to `pom.xml` with version property set to `LATEST`
- [ ] Set Java compiler source/target to 17+ in `pom.xml`
- [ ] Create handler class extending `DurableHandler<TInput, TOutput>`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need mvn install?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

var f1 = ctx.stepAsync("batch-1", BatchResult.class,
s -> ctx.invoke("invoke-1", childArn, event.getBatches().get(0), BatchResult.class));
var f2 = ctx.stepAsync("batch-2", BatchResult.class,
s -> ctx.invoke("invoke-2", childArn, event.getBatches().get(1), BatchResult.class));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can start any durable operation in a step, including invoke in this case


var approved = childCtx.waitForCallback("approval", ApprovalData.class,
(callbackId, s) -> sendApproval(item, callbackId),
CallbackConfig.builder().timeout(Duration.ofHours(24)).build());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitForCallback accepts WaitForCallbackConfig, not CallbackConfig.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed lines 779-782 in advanced-patterns.md

var id = UUID.randomUUID().toString(); // Different UUID each time
var timestamp = System.currentTimeMillis(); // Different timestamp each time
var random = Math.random(); // Different random number
ctx.step("save", Void.class, s -> { saveData(id, timestamp); return null; });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with this step operation?

.initialDelay(Duration.ofSeconds(5))
.maxDelay(Duration.ofSeconds(30))
.backoffRate(1.5)))
.shouldContinuePolling(state -> !"completed".equals(state.getStatus()))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no shouldContinuePolling configuration in WaitForConditionConfig. The decision is made inside the user function directly when returning a new state.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

boolean shouldContinue = state.getData() == null || !state.getData().isReady();
return shouldContinue
? WaitForConditionResult.continuePolling(delay)
: WaitForConditionResult.stopPolling();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. In Java, the decision is made in the user function directly

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

"Reject: " + approvalUrl + "?callback=" + callbackId + "&action=reject"
));
},
CallbackConfig.builder().timeout(Duration.ofHours(48)).build());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. waitForCallback accepts WaitForCallbackConfig

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@smoell
Copy link
Copy Markdown
Author

smoell commented Apr 7, 2026

Fixed open issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants