Skip to main content

Serverless Workers on AWS Lambda - Java SDK

The temporal-aws-lambda contrib module lets you run a Temporal Serverless Worker on AWS Lambda. Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive. Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline. You register Workflows and Activities the same way you would with a standard Worker.

For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see Deploy a Serverless Worker.

Create and run a Worker in Lambda

Add the temporal-aws-lambda dependency alongside the Temporal SDK. Create a Lambda handler class that delegates to LambdaWorker.run.

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import io.temporal.aws.lambda.LambdaWorker;
import io.temporal.common.WorkerDeploymentVersion;
import io.temporal.workflow.Workflow;

public final class Handler implements RequestHandler<Object, Void> {
private static final RequestHandler<Object, Void> WORKER =
LambdaWorker.run(
new WorkerDeploymentVersion("my-app", "build-1"),
options ->
options
.setTaskQueue("serverless-task-queue-1")
.registerWorkflowImplementationTypes(SampleWorkflowImpl.class)
.registerActivitiesImplementations(new SampleActivitiesImpl()));

@Override
public Void handleRequest(Object input, Context context) {
return WORKER.handleRequest(input, context);
}
}

LambdaWorker.run takes a WorkerDeploymentVersion and a configure callback, and returns a RequestHandler. Assign the handler to a static field so it is created once during Lambda cold start and reused across invocations.

The WorkerDeploymentVersion is required. Worker Deployment Versioning is always enabled for Serverless Workers. Each Workflow must have a versioning behavior, either AutoUpgrade or Pinned. Set it per-Workflow at registration time, or set a worker-level default with DefaultVersioningBehavior in DeploymentOptions.

The configure callback exposes the same registration methods as a standard Worker: registerWorkflowImplementationTypes, registerActivitiesImplementations, and registerDynamicWorkflowImplementationType.

If you need to assemble options outside the callback, call LambdaWorkerOptions.fromEnvironment(), mutate the returned options, and pass them to LambdaWorker.newHandler(...).

Configure the Temporal connection

The temporal-aws-lambda module automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to Environment Configuration for more details.

The config file is resolved in order:

  1. TEMPORAL_CONFIG_FILE environment variable, if set.
  2. temporal.toml in $LAMBDA_TASK_ROOT (typically /var/task).
  3. temporal.toml in the current working directory.

The file is optional. If absent, only environment variables are used.

Encrypt sensitive values like TLS keys or API keys. Refer to AWS documentation for options.

Adjust Worker defaults for Lambda

The temporal-aws-lambda module applies conservative defaults suited to short-lived Lambda invocations. These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment.

SettingLambda default
MaxConcurrentActivityExecutionSize2
MaxConcurrentWorkflowTaskExecutionSize10
MaxConcurrentLocalActivityExecutionSize2
MaxConcurrentNexusExecutionSize5
MaxConcurrentWorkflowTaskPollers2
MaxConcurrentActivityTaskPollers1
MaxConcurrentNexusTaskPollers1
WorkflowCacheSize30
MaxWorkflowThreadCount30
GracefulShutdownTimeout5 seconds
ShutdownDeadlineBuffer7 seconds

Eager Activities are not supported. Lambda invocations don't maintain persistent connections.

ShutdownDeadlineBuffer is specific to the temporal-aws-lambda module. It controls the full shutdown window reserved at the end of the Lambda invocation, including graceful shutdown time, shutdown hooks, and service stub cleanup. The default is GracefulShutdownTimeout (5s) + 2s.

If you change GracefulShutdownTimeout without explicitly setting ShutdownDeadlineBuffer, the buffer is recomputed as GracefulShutdownTimeout + 2s. If you explicitly set ShutdownDeadlineBuffer, it must be greater than or equal to GracefulShutdownTimeout.

If your Worker handles long-running Activities, increase GracefulShutdownTimeout, ShutdownDeadlineBuffer, and the Lambda invocation deadline (--timeout) together. For guidance on how these values relate, see Tuning for long-running Activities.

Add observability with OpenTelemetry

The OtelLambdaWorker class provides OpenTelemetry integration with defaults configured for the AWS Distro for OpenTelemetry (ADOT) Lambda layer. With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions. The ADOT Lambda layer collects this telemetry and can forward traces to AWS X-Ray and metrics to Amazon CloudWatch.

The underlying metrics and traces are the same ones the Java SDK emits in any environment. For general observability concepts and the full list of available metrics, see Observability - Java SDK and the SDK metrics reference.

import io.temporal.aws.lambda.LambdaWorker;
import io.temporal.aws.lambda.OtelLambdaWorker;
import io.temporal.common.WorkerDeploymentVersion;

public final class Handler implements RequestHandler<Object, Void> {
private static final RequestHandler<Object, Void> WORKER =
LambdaWorker.run(
new WorkerDeploymentVersion("my-app", "build-1"),
options -> {
OtelLambdaWorker.configure(options);
options
.setTaskQueue("serverless-task-queue-1")
.registerWorkflowImplementationTypes(SampleWorkflowImpl.class)
.registerActivitiesImplementations(new SampleActivitiesImpl());
});

@Override
public Void handleRequest(Object input, Context context) {
return WORKER.handleRequest(input, context);
}
}

OtelLambdaWorker.configure creates an OpenTelemetry SDK with OTLP metric and trace exporters, uses AWS X-Ray-compatible trace ID generation, installs an OpenTelemetry-backed metrics scope, and registers per-invocation flush hooks. By default, telemetry is sent to localhost:4317, which is the ADOT Lambda layer's default collector endpoint. The endpoint can be overridden with the OTEL_EXPORTER_OTLP_ENDPOINT environment variable.

To collect this telemetry, attach the ADOT Collector layer to your Lambda function. Java does not need a language-specific ADOT layer because the OTel SDK is included as a dependency of the module.

The default Collector configuration does not route OpenTelemetry Protocol (OTLP) data to the traces pipeline. You must provide a custom Collector configuration that wires the OTLP receiver to both the traces and metrics pipelines. Bundle the following otel-collector-config.yaml in your Lambda deployment package:

receivers:
otlp:
protocols:
grpc:
endpoint: "localhost:4317"
http:
endpoint: "localhost:4318"

exporters:
debug:
awsxray:
region: us-west-2
awsemf:
namespace: TemporalWorkerMetrics
log_group_name: /aws/lambda/<your-function-name>
region: us-west-2
dimension_rollup_option: NoDimensionRollup
resource_to_telemetry_conversion:
enabled: true

service:
pipelines:
traces:
receivers: [otlp]
exporters: [awsxray, debug]
metrics:
receivers: [otlp]
exporters: [awsemf]
telemetry:
logs:
level: debug
metrics:
address: localhost:8888

Set the following environment variable on the Lambda function to point the Collector at the bundled config:

  • OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml

Enable X-Ray active tracing on the Lambda function:

aws lambda update-function-configuration \
--function-name <your-function-name> \
--tracing-config Mode=Active

The Lambda execution role must have permissions to write to X-Ray and CloudWatch. Add xray:PutTraceSegments, xray:PutTelemetryRecords, and cloudwatch:PutMetricData permissions to the execution role. Without these permissions, the Collector fails silently and no telemetry appears.

If you only need metrics or tracing, use OtelLambdaWorker.configureMetrics, OtelLambdaWorker.configureTracing, or OtelLambdaWorker.configureFlushHook individually. To use an application-owned OpenTelemetry provider, call builder.setOpenTelemetry(...) instead. In that path, no exporters are created and the helper only installs the metrics scope, interceptors, and per-invocation flush hook.