Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
55509cb
Add default constructor support for OTel plugin
zhongkechen Jul 14, 2026
7677f17
Add cloud coverage for default OTel plugin constructor
zhongkechen Jul 14, 2026
24c8830
Fix Spotless formatting
zhongkechen Jul 14, 2026
1bf20f7
Use async start for ADOT default constructor e2e
zhongkechen Jul 14, 2026
d17784d
Use global SDK provider for default OTel e2e
zhongkechen Jul 14, 2026
85cd24c
Improve cloud OTel history diagnostics
zhongkechen Jul 14, 2026
704c01a
Fix OTel cloud test formatting
zhongkechen Jul 14, 2026
3f51759
Use ADOT distro Java layer for OTel examples
zhongkechen Jul 14, 2026
e6ebf21
Join ADOT current context for default OTel plugin
zhongkechen Jul 14, 2026
473c821
Remove ADOT wrapper from OTel examples
zhongkechen Jul 14, 2026
d0e4709
Make default OTel plugin use OTLP exporter fallback
zhongkechen Jul 14, 2026
0dbae73
Log default OTel plugin provider source
zhongkechen Jul 14, 2026
7a502d5
Use ADOT Java instrumentation wrapper
zhongkechen Jul 14, 2026
7d7b0a6
Log global OTel provider copy failures
zhongkechen Jul 14, 2026
2eacd2c
Use ADOT Java agent global tracer provider
zhongkechen Jul 14, 2026
eab94b5
Install deterministic IDs in ADOT Java agent provider
zhongkechen Jul 14, 2026
5085a3b
Install Java agent ID generator in provider branch
zhongkechen Jul 14, 2026
cd26446
Use OTel autoconfigure SPI for agent ID generation
zhongkechen Jul 14, 2026
d149fe6
Configure OTel agent extension in examples template
zhongkechen Jul 14, 2026
58270e5
Handle ADOT agent provider classloader split
zhongkechen Jul 14, 2026
34429f0
Require OTel SPI for default plugin constructor
zhongkechen Jul 14, 2026
ffb8c74
Rely on OTel SPI for default plugin provider
zhongkechen Jul 14, 2026
1a7ba9d
Avoid loading OTel SPI provider from plugin constructor
zhongkechen Jul 14, 2026
187a463
Package OTel autoconfigure SPI at runtime
zhongkechen Jul 14, 2026
3229eba
Point ADOT extension config to plugin jar
zhongkechen Jul 14, 2026
4adeaf0
Package OTel plugin jar for ADOT extension
zhongkechen Jul 14, 2026
8549dee
Remove OTel SPI debug helper
zhongkechen Jul 14, 2026
01317a7
Limit ADOT Java agent to default constructor example
zhongkechen Jul 14, 2026
f40792a
Update OTel examples for ADOT Java agent
zhongkechen Jul 14, 2026
1cad798
fix(otel): Use operation startTimestamp for continuation spans (#542)
SilanHe Jul 16, 2026
75a0d61
[maven]: Bump com.fasterxml.jackson:jackson-bom from 2.21.4 to 2.22.1…
dependabot[bot] Jul 20, 2026
b82abe9
[maven]: Bump software.amazon.awssdk:bom from 2.46.21 to 2.48.3 (#549)
dependabot[bot] Jul 20, 2026
45f004b
[github-actions]: Bump slackapi/slack-github-action (#548)
dependabot[bot] Jul 20, 2026
4485120
feat(conformance): add Java conformance test package (step, wait suit…
wangyb-A Jul 21, 2026
9f5e58a
[maven]: Bump io.opentelemetry:opentelemetry-exporter-logging (#550)
dependabot[bot] Jul 21, 2026
903a436
feat(examples): Parameterize ADOT layer ARN by region
ayushiahjolia Jul 21, 2026
d77c980
Merge branch 'main' into codex/otel-plugin-default-constructor
ayushiahjolia Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@ jobs:
- name: Generate SAM template
run: python3 generate-template.py
working-directory: ./examples
- name: Resolve latest ADOT Java layer ARN
env:
GH_TOKEN: ${{ github.token }}
run: |
# The ADOT Java layer is regional: account ID and version vary per region.
# Resolve the current ARN for AWS_REGION from the release notes table (region | ARN).
ADOT_LAYER_ARN=$(
gh api repos/aws-observability/aws-otel-java-instrumentation/releases/latest \
--jq .body |
awk -F '|' -v region="$AWS_REGION" '
$2 ~ "^[[:space:]]*" region "[[:space:]]*$" {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $3)
print $3
exit
}
'
)
if [ -z "$ADOT_LAYER_ARN" ]; then
echo "Could not resolve the latest ADOT Java layer for $AWS_REGION"
exit 1
fi
aws lambda get-layer-version-by-arn \
--arn "$ADOT_LAYER_ARN" \
--region "$AWS_REGION" \
--query LayerVersionArn \
--output text > /dev/null
echo "Using ADOT Java layer $ADOT_LAYER_ARN"
echo "ADOT_LAYER_ARN=$ADOT_LAYER_ARN" >> "$GITHUB_ENV"
- name: sam build
env:
MAVEN_OPTS: -DskipTests=true -Dmaven.test.skip=true
Expand All @@ -87,7 +115,7 @@ jobs:
run: |
sam deploy --stack-name Java${{ matrix.java }}-JavaSDKCloudBasedIntegrationTestStack \
--resolve-image-repos --resolve-s3 --parameter-overrides \
'ParameterKey=Architecture,ParameterValue=x86_64 ParameterKey=JavaVersion,ParameterValue=java${{ matrix.java }} ParameterKey=FunctionNamePrefix,ParameterValue=Java${{ matrix.java }}- ParameterKey=RoleArn,ParameterValue=${{ secrets.TEST_LAMBDA_EXECUTION_ROLE_ARN }}'
'ParameterKey=Architecture,ParameterValue=x86_64 ParameterKey=JavaVersion,ParameterValue=java${{ matrix.java }} ParameterKey=FunctionNamePrefix,ParameterValue=Java${{ matrix.java }}- ParameterKey=RoleArn,ParameterValue=${{ secrets.TEST_LAMBDA_EXECUTION_ROLE_ARN }} ParameterKey=AdotLayerArn,ParameterValue='"$ADOT_LAYER_ARN"
working-directory: ./examples
- name: Cloud Based Integration Tests
run: |
Expand Down
18 changes: 18 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,26 @@ The SAM template configures:
- `DurableConfig` with `ExecutionTimeout` and `RetentionPeriodInDays`
- CloudWatch log groups for Lambda functions with 7 days of retention
- IAM permissions for `lambda:CheckpointDurableExecutions` and `lambda:GetDurableExecutionState`
- ADOT tracing examples with active X-Ray tracing and the ADOT Lambda layer
- OTel examples that use `new OtelPlugin()` with `AWS_LAMBDA_EXEC_WRAPPER` and `OTEL_JAVAAGENT_EXTENSIONS` so the OTel plugin SPI is loaded by the Java agent

`template.yaml` is generated from the Java example handlers and is intentionally not checked in. Re-run `python3 generate-template.py` after adding or removing a deployable example handler.

The examples package copies the OTel plugin jar into `lib/` so the ADOT Java agent can load it as an extension for examples that enable `OTEL_JAVAAGENT_EXTENSIONS`.

### ADOT layer region

The ADOT layer is regional — its account ID and version vary by region — so the template exposes it as the `AdotLayerArn` parameter. The default targets `us-west-2` (the region used by the e2e tests), and the e2e workflow resolves the current ARN for its region automatically.

If you deploy the tracing examples to any other region, override the parameter with that region's ARN, otherwise the deploy fails CloudFormation's `ResourceExistenceCheck` on a cross-region layer:

```bash
sam deploy --parameter-overrides \
AdotLayerArn=arn:aws:lambda:us-east-1:615299751070:layer:AWSOpenTelemetryDistroJava:16
```

Find the current ARN for your region in the [ADOT Java instrumentation releases](https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest). You can also persist the override in your (git-ignored) `samconfig.toml` under `parameter_overrides`.

## Invoke Deployed Functions

```bash
Expand Down Expand Up @@ -96,6 +113,7 @@ mvn test -Dtest=CloudBasedIntegrationTest \
| [CustomShouldCompleteMapExample](src/main/java/software/amazon/lambda/durable/examples/map/CustomShouldCompleteMapExample.java) | Custom map completion with `shouldComplete` decisions |
| [WaitForConditionExample](src/main/java/software/amazon/lambda/durable/examples/wait/WaitForConditionExample.java) | Poll a condition until met with `waitForCondition()` |
| [OtelExample](src/main/java/software/amazon/lambda/durable/examples/general/OtelExample.java) | OpenTelemetry instrumentation with logging span export |
| [OtelXRayDefaultConstructorExample](src/main/java/software/amazon/lambda/durable/examples/otel/OtelXRayDefaultConstructorExample.java) | Export spans to X-Ray with `new OtelPlugin()` and no handler-side OpenTelemetry initialization |
| [OtelXRayStepExample](src/main/java/software/amazon/lambda/durable/examples/otel/OtelXRayStepExample.java) | Export step spans to X-Ray through the ADOT Lambda Layer |
| [OtelXRayWaitExample](src/main/java/software/amazon/lambda/durable/examples/otel/OtelXRayWaitExample.java) | Trace a step-wait-step workflow across Lambda invocations |
| [OtelXRayMapExample](src/main/java/software/amazon/lambda/durable/examples/otel/OtelXRayMapExample.java) | Trace concurrent map operations and item steps in X-Ray |
Expand Down
53 changes: 43 additions & 10 deletions examples/generate-template.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import argparse
import re
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path

Expand All @@ -13,6 +14,17 @@
EXAMPLE_PACKAGE_ROOT = SOURCE_ROOT / "software/amazon/lambda/durable/examples"
DEFAULT_OUTPUT = EXAMPLES_DIR / "template.yaml"
TEMPLATE_ANNOTATION = "ExampleTemplate"
POM_NAMESPACE = {"m": "http://maven.apache.org/POM/4.0.0"}


def read_otel_plugin_jar_path() -> str:
root = ET.parse(EXAMPLES_DIR / "pom.xml").getroot()
version = root.findtext("m:version", namespaces=POM_NAMESPACE)
if version is None:
version = root.findtext("m:parent/m:version", namespaces=POM_NAMESPACE)
if version is None:
raise ValueError("Unable to read examples version from pom.xml")
return f"/var/task/lib/aws-durable-execution-sdk-java-plugin-otel-{version}.jar"


@dataclass(frozen=True)
Expand All @@ -22,6 +34,7 @@ class ExampleFunction:
suffix: str
condition: str | None
tracing: bool
java_agent: bool

@property
def logical_id(self) -> str:
Expand Down Expand Up @@ -58,24 +71,26 @@ def is_top_level_durable_handler(source: str, class_name: str) -> bool:
return bool(match and "extends DurableHandler" in match.group("header"))


def read_template_annotation(source: str, class_name: str) -> tuple[str | None, bool]:
def read_template_annotation(source: str, class_name: str) -> tuple[str | None, bool, bool]:
class_match = re.search(rf"public\s+(?:final\s+)?class\s+{class_name}\b", source)
if not class_match:
return None, False
return None, False, False

prefix = source[: class_match.start()]
matches = list(
re.finditer(rf"@(?:[A-Za-z_][\w.]*\.)?{TEMPLATE_ANNOTATION}\s*(?:\((?P<body>.*?)\))?", prefix, re.DOTALL)
)
if not matches:
return None, False
return None, False, False

body = matches[-1].group("body") or ""
condition_match = re.search(r'condition\s*=\s*"([^"]+)"', body)
tracing_match = re.search(r"tracing\s*=\s*(true|false)", body)
java_agent_match = re.search(r"javaAgent\s*=\s*(true|false)", body)
condition = condition_match.group(1) if condition_match else None
tracing = tracing_match.group(1) == "true" if tracing_match else False
return condition, tracing
java_agent = java_agent_match.group(1) == "true" if java_agent_match else False
return condition, tracing, java_agent


def discover_examples() -> list[ExampleFunction]:
Expand All @@ -86,7 +101,7 @@ def discover_examples() -> list[ExampleFunction]:
if not is_top_level_durable_handler(source, class_name):
continue

condition, tracing = read_template_annotation(source, class_name)
condition, tracing, java_agent = read_template_annotation(source, class_name)
package_name = read_package(source, path)
examples.append(
ExampleFunction(
Expand All @@ -95,12 +110,13 @@ def discover_examples() -> list[ExampleFunction]:
suffix=kebab_case(class_name),
condition=condition,
tracing=tracing,
java_agent=java_agent,
)
)
return examples


def emit_function(lines: list[str], example: ExampleFunction) -> None:
def emit_function(lines: list[str], example: ExampleFunction, java_agent_extension_path: str) -> None:
lines.extend(
[
f" {example.logical_id}:",
Expand All @@ -123,9 +139,17 @@ def emit_function(lines: list[str], example: ExampleFunction) -> None:
[
" Tracing: Active",
" Layers:",
" - !Sub",
" - arn:aws:lambda:${AWS::Region}:901920570463:layer:aws-otel-java-agent-${AdotArch}-ver-1-32-0:6",
" - AdotArch: amd64",
" - !Ref AdotLayerArn",
]
)
if example.java_agent:
lines.extend(
[
" Environment:",
" Variables:",
" AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument",
f' JAVA_TOOL_OPTIONS: "-Dotel.javaagent.extensions={java_agent_extension_path}"',
f" OTEL_JAVAAGENT_EXTENSIONS: {java_agent_extension_path}",
]
)
lines.append("")
Expand All @@ -151,6 +175,7 @@ def emit_log_group(lines: list[str], example: ExampleFunction) -> None:


def render_template(examples: list[ExampleFunction]) -> str:
java_agent_extension_path = read_otel_plugin_jar_path()
lines = [
"# This file is generated by examples/generate-template.py. Do not edit it by hand.",
'AWSTemplateFormatVersion: "2010-09-09"',
Expand All @@ -176,6 +201,14 @@ def render_template(examples: list[ExampleFunction]) -> str:
" RoleArn:",
" Type: String",
" Description: IAM Role ARN for Lambda function execution",
" AdotLayerArn:",
" Type: String",
" Default: arn:aws:lambda:us-west-2:615299751070:layer:AWSOpenTelemetryDistroJava:16",
" Description: >-",
" ARN of the ADOT (AWS Distro for OpenTelemetry) Lambda layer used by the tracing examples.",
" The layer is regional: its account ID and version vary by region, so override this per",
" deployment region. The default targets us-west-2 (the region used by e2e tests); CI",
" resolves the latest ARN for its region.",
"",
"Conditions:",
" IsJava21OrLater:",
Expand All @@ -202,7 +235,7 @@ def render_template(examples: list[ExampleFunction]) -> str:

for example in examples:
emit_log_group(lines, example)
emit_function(lines, example)
emit_function(lines, example, java_agent_extension_path)

lines.append("Outputs:")
for index, example in enumerate(examples):
Expand Down
36 changes: 24 additions & 12 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,6 @@
<version>1.64.0</version>
</dependency>

<!-- OTLP gRPC exporter (sends spans to ADOT collector layer at localhost:4317) -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.64.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.82.2</version>
</dependency>

<!-- AWS Lambda Java Core -->
<dependency>
<groupId>com.amazonaws</groupId>
Expand Down Expand Up @@ -154,6 +142,30 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.9.0</version>
<executions>
<execution>
<id>copy-otel-javaagent-extension</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>aws-durable-execution-sdk-java-plugin-otel</artifactId>
<version>${project.version}</version>
<outputDirectory>${project.build.outputDirectory}/lib</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
String condition() default "";

boolean tracing() default false;

boolean javaAgent() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@
* <li>Logging exporter (spans printed to stdout → CloudWatch Logs)
* </ul>
*
* <p>In production, replace {@code LoggingSpanExporter} with {@code OtlpGrpcSpanExporter} to send spans to an OTLP
* collector (X-Ray, Datadog, etc.).
* <p>In production, replace {@code LoggingSpanExporter} with the exporter for your observability backend.
*
* <p>Expected trace structure:
*
* <pre>
* durable.invocation
* ├── durable.step:create-greeting [attempt 1]
* ── durable.step:create-greeting (operation, backfilled)
* ── durable.step:transform [attempt 1]
* └── durable.step:transform (operation, backfilled)
* invocation
* ├── create-greeting
* │ └── create-greeting attempt 1
* ── transform
* └── transform attempt 1
* </pre>
*/
public class OtelExample extends DurableHandler<GreetingRequest, String> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.examples.otel;

import software.amazon.lambda.durable.DurableConfig;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.examples.ExampleTemplate;
import software.amazon.lambda.durable.examples.types.GreetingRequest;
import software.amazon.lambda.durable.otel.OtelPlugin;

/**
* OTel + X-Ray example that uses the no-arg plugin constructor.
*
* <p>{@link OtelPlugin#OtelPlugin()} uses the global provider initialized by the ADOT Java agent with deterministic
* span ID generation installed by the plugin's OpenTelemetry autoconfigure SPI.
*/
@ExampleTemplate(tracing = true, javaAgent = true)
public class OtelXRayDefaultConstructorExample extends DurableHandler<GreetingRequest, String> {

@Override
protected DurableConfig createConfiguration() {
return DurableConfig.builder().withPlugins(new OtelPlugin()).build();
}

@Override
public String handleRequest(GreetingRequest input, DurableContext context) {
context.getLogger().info("Starting OTel X-Ray default constructor example for {}", input.getName());

var greeting = context.step("default-create-greeting", String.class, stepCtx -> "Hello, " + input.getName());

var result = context.step("default-transform", String.class, stepCtx -> greeting.toUpperCase() + "!");

context.getLogger().info("OTel X-Ray default constructor example complete: {}", result);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.examples.otel;

import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.util.List;
Expand All @@ -20,10 +20,9 @@ public final class OtelXRayExamples {

private OtelXRayExamples() {}

private static DurableConfig otelConfig() {
var otlpExporter = OtlpGrpcSpanExporter.getDefault();
var otelPlugin =
new OtelPlugin(SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));
private static DurableConfig localOtelConfig() {
var otelPlugin = new OtelPlugin(
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())));
return DurableConfig.builder().withPlugins(otelPlugin).build();
}

Expand All @@ -32,7 +31,7 @@ public static class MapExample extends DurableHandler<GreetingRequest, String> {

@Override
protected DurableConfig createConfiguration() {
return otelConfig();
return localOtelConfig();
}

@Override
Expand All @@ -56,7 +55,7 @@ public static class ParallelExample extends DurableHandler<GreetingRequest, Stri

@Override
protected DurableConfig createConfiguration() {
return otelConfig();
return localOtelConfig();
}

@Override
Expand Down Expand Up @@ -85,7 +84,7 @@ public static class NestedContextExample extends DurableHandler<GreetingRequest,

@Override
protected DurableConfig createConfiguration() {
return otelConfig();
return localOtelConfig();
}

@Override
Expand Down
Loading
Loading