Java Serverless on AWS Lambda · Frameworks, Native Images and SDK Clients
AWS SDK for Java v2: Clients, HTTP and Retries
Why creating SDK clients per request makes a Java function slow and leaky, and how to configure clients for Lambda.
Scenario
Example app: a Java function reads from DynamoDB and publishes to SQS. Each invocation builds new clients with DynamoDbClient.create() and SqsClient.create() and never closes them.
What you see in production
Every request is 150 to 300 ms slower than necessary; after many invocations in a long-lived environment, memory use creeps up and occasional Unable to execute HTTP request: Timeout waiting for connection from pool errors appear.
The naive code
public String handleRequest(Map<String, String> input, Context ctx) {
DynamoDbClient dynamo = DynamoDbClient.create(); // credential chain, region lookup, new HTTP pool
SqsClient sqs = SqsClient.create();
var item = dynamo.getItem(r -> r.tableName("orders").key(key(input)));
sqs.sendMessage(r -> r.queueUrl(QUEUE_URL).messageBody(toJson(item)));
return "ok"; // clients never closed
}
Reproduce it
public class Main {
static int clientsCreated = 0, openPools = 0;
static final class Client {
Client() { clientsCreated++; openPools++; }
}
public static void main(String[] args) {
int invocations = 1000;
int setupMsPerClient = 120; // illustrative
for (int i = 0; i < invocations; i++) { new Client(); new Client(); }
System.out.printf("per request: %d clients, %d open pools, %d ms spent on setup%n",
clientsCreated, openPools, clientsCreated * setupMsPerClient);
clientsCreated = 0; openPools = 0;
Client dynamo = new Client(), sqs = new Client(); // static fields: created at init
System.out.printf("shared: %d clients, %d open pools, %d ms spent on setup%n",
clientsCreated, openPools, clientsCreated * setupMsPerClient);
}
}
Output
per request: 2000 clients, 2000 open pools, 240000 ms spent on setup shared: 2 clients, 2 open pools, 240 ms spent on setup
Run this on your computer — see “Get Started” for setup.
Root cause
SDK clients are heavyweight: building one resolves credentials and region through provider chains and creates an HTTP client with a connection pool. Clients are thread-safe and designed to be long-lived and shared. Creating them per request repeats the setup and, without close(), leaks connection pools and threads.
The fix
Create clients once (static fields or the handler's constructor), configured explicitly for Lambda:
public class OrderHandler implements RequestHandler<Map<String, String>, String> {
private static final SdkHttpClient HTTP = AwsCrtHttpClient.builder().build();
private static final DynamoDbClient DYNAMO = DynamoDbClient.builder()
.region(Region.of(System.getenv("AWS_REGION")))
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.httpClient(HTTP)
.overrideConfiguration(c -> c.retryStrategy(AwsRetryStrategy.standardRetryStrategy())
.apiCallTimeout(Duration.ofSeconds(3)))
.build();
@Override
public String handleRequest(Map<String, String> input, Context ctx) {
var item = DYNAMO.getItem(r -> r.tableName("orders").key(key(input)));
// ...
return "ok";
}
}
Set timeouts (apiCallTimeout, apiCallAttemptTimeout) shorter than the function timeout, and keep SDK retries modest so that, combined with Lambda's own retries for asynchronous sources, they do not multiply load during an outage.
Trade-offs
- The AWS CRT HTTP client starts fast and performs well but is a native library (check architecture support);
UrlConnectionHttpClientis lightest but has fewer features; the Apache client has the most options. - Async clients (
DynamoDbAsyncClient) enable concurrent calls but add complexity; virtual threads with sync clients are an alternative on Java 21. - Explicit credentials providers are faster but must match how the function runs (Lambda provides credentials through environment variables).
Detect it in production
- Per-request latency with an SDK setup component in traces; connection-pool timeout errors.
- Thread and memory growth over an environment's life.
- Retry counts in SDK metrics; total attempts during downstream incidents.
Key points
- SDK clients are heavyweight and thread-safe: create once, share, reuse.
- Configure region, credentials provider, HTTP client and timeouts explicitly.
- Keep SDK retries modest to avoid retry multiplication.
Further reading: AWS SDK for Java v2: reduce SDK startup time for Lambda · AWS SDK for Java v2: HTTP clients
Practice questions
Why should AWS SDK for Java v2 clients be created once per execution environment?
Answer
Clients are expensive to build (credential and region resolution, HTTP client and connection pool) and are thread-safe for reuse; creating them per request adds latency and, if not closed, leaks resources.
Which HTTP client options exist for the SDK on Lambda, and how do they differ?
Answer
The AWS CRT-based client (fast start-up and high performance, native library), UrlConnectionHttpClient (lightweight, minimal features) and the Apache client (most configurable, heavier); Netty is used for async clients.
How can SDK retries and Lambda retries multiply load during an outage?
Answer
For asynchronous or event-source invocations, Lambda retries the whole invocation, and inside each attempt the SDK retries every call. Several layers of retries multiply requests to the struggling dependency; keep SDK retries modest and set timeouts.
Open in the app to mark lessons complete, take notes and track your progress.