TL;DR: Interviews for Java engineers on AWS test the SDK for Java 2.x, Java on Lambda (SnapStart hooks, priming, tiered compilation) and the JVM in containers. SDK 1.x reached end of support on December 31, 2025, and Lambda added a java25 runtime on November 14, 2025. Senior answers also cover Aurora failover through the AWS Advanced JDBC Wrapper.
On June 1, 2026, AWS SDK for Java 2.46.0 swapped its default sync HTTP client from Apache HttpClient 4.x to 5.x. Apache 5 uses different logger names, so log filters written for the old client no longer match it.
Generic AWS questions live in our AWS interview guide; the 30 below are about Java.
- 1Each SDK client's HTTP pool defaults to 50 connections with a 10 second acquire timeout, the first wall a virtual-thread fan-out hits.
- 2On Java managed runtimes, SnapStart costs nothing extra; Python and .NET pay caching and restore charges.
- 3The JVM sizes its heap at 25% of container memory by default. An untuned 4 GB Fargate task gets about 1 GB.
- 4The DynamoDB Enhanced Client's version attribute does not protect deleteItem; deletes need their own condition expression.
SDK for Java 2.x
1. How does the AWS SDK for Java 2.x differ from 1.x?
Version 2.x is a rewrite, not an upgrade. Packages move from com.amazonaws to software.amazon.awssdk. Clients and requests are immutable and built with builders. Async clients return CompletableFuture.
The two versions can run side by side during a migration, per the migration guide.
AWS ships an OpenRewrite-based migration tool that rewrites service client and S3 Transfer Manager code. A strong candidate knows its main gap. It does not convert DynamoDBMapper code to the DynamoDB Enhanced Client, so that part is manual.
2. Why should you reuse one SDK client, and is it thread-safe?
Yes, every service client is thread-safe, and you should share one per configuration. Each client owns its own HTTP connection pool, so creating a client per request throws away warm connections and leaks threads.
The SDK best practices page says to share a single instance and call close() on any client you no longer need.
Two follow-ups show real experience. First, a ResponseInputStream from getObject holds its connection until you read and close it. Forget that and the pool drains.
Second, the pool size. the SDK's HTTP defaults are 50 connections, a 2 second connect timeout and a 10 second acquire timeout.
3. Which HTTP client would you configure, and why?
The CRT-based client where possible, Apache 5 or Netty when the app depends on JVM TLS system properties. The SDK has three sync clients: Apache 5, AWS CRT and URLConnection. The old Apache 4 client is deprecated.
The async clients are Netty and AWS CRT. The HTTP configuration docs recommend the CRT clients "if possible" and name them as the choice for Lambda.

The trade-offs matter. The CRT clients ignore the javax.net.ssl.keyStore and trustStore properties. Mutual TLS set up that way needs Apache 5 or Netty. The URLConnection client keeps the dependency tree small.
But it cannot send PATCH, which some API Gateway update calls need, and has no idle timeout setting.
4. When do you use the async client, and what can still block?
Use the async client when one thread must keep many calls in flight, such as an S3 fan-out. It returns CompletableFuture and runs I/O on a small pool. The catch, stated in the async programming docs: non-blocking I/O "is not guaranteed".
Credential retrieval, SigV4 signing and endpoint discovery can block.
Completion callbacks run on the SDK's future-completion executor unless you pass your own through SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR. Run heavy work inside thenApply on your own executor.
Blocking in a callback ties up the threads that finish other calls on that client.
5. Walk through the default credentials provider chain.
The SDK checks six sources in order. It starts with Java system properties, environment variables and a web identity token with a role ARN.
Then it reads the shared credentials and config files, ECS container credentials and finally EC2 instance profile credentials. The first provider that finds a full set wins, per the credentials chain docs.
Each platform feeds one step. EKS injects the web identity token file, and the ECS agent sets AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.
Lambda exposes the execution role's keys as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN. The order also explains a classic bug.
Leftover keys in a build agent's environment beat the instance role, so the job runs as the wrong identity.
6. How do retries and timeouts work in SDK 2.x by default?
Clients use the legacy retry strategy unless told otherwise: 4 attempts, with DynamoDB clients allowed 8 retries. The retry docs call legacy deprecated and recommend standard (3 attempts, 1 second base delay for throttling).
The adaptive strategy adds a client-side rate limiter. It assumes the client talks to one resource, such as one table, so give each resource its own client.
The SDK sets connection and socket timeouts but no API call timeout, so set both levels yourself:
private static final DynamoDbClient DDB = DynamoDbClient.builder()
.overrideConfiguration(o -> o
.retryStrategy(RetryMode.STANDARD)
.apiCallAttemptTimeout(Duration.ofMillis(800))
.apiCallTimeout(Duration.ofSeconds(5)))
.build();
apiCallAttemptTimeout caps one HTTP attempt; apiCallTimeout caps the whole call including retries.
7. What are paginators and waiters for?
Paginators hide continuation tokens; waiters hide polling loops. A method such as listObjectsV2Paginator returns an iterable that fetches each page on demand. Its contents() view streams objects instead of pages, per the pagination docs.
s3.listObjectsV2Paginator(r -> r.bucket(bucket).prefix("orders/"))
.contents()
.forEach(obj -> archive(obj.key()));
ddb.waiter().waitUntilTableExists(r -> r.tableName("orders"));
Hand-written pagination loops are a frequent source of bugs that drop the last page. The waiters utility polls until a resource reaches a state or never will. That beats a Thread.sleep loop in setup code and tests.
DynamoDB and S3
8. Bean or immutable schema in the DynamoDB Enhanced Client?
Both map Java classes to items; they differ in how objects are built. A bean schema uses @DynamoDbBean with getters and setters and TableSchema.fromBean().
An immutable schema uses @DynamoDbImmutable(builder = ...), final fields and a builder with a no-argument build(). You load it with TableSchema.fromImmutableClass(), per the immutable data class docs.
- @DynamoDbBean, getters and setters
- Mutable, simplest to start with
- Loaded with TableSchema.fromBean()
- @DynamoDbImmutable with a builder class
- Safe to share across threads, works with Lombok
- Loaded with TableSchema.fromImmutableClass()
Build the TableSchema once and keep it in a static field. The TableSchema docs call it "designed to be static and immutable", and fromBean() introspects the class each time it runs.
A static schema built with TableSchema.builder() skips that introspection.
9. How do you implement optimistic locking with the Enhanced Client?
Annotate a numeric attribute with @DynamoDbVersionAttribute. The VersionedRecordExtension is loaded by default. It adds a condition to each put and update. The write fails if the stored version differs from the one you read.
On success it increments the version.
@DynamoDbBean
public class Order {
private String id;
private Long version;
@DynamoDbPartitionKey
public String getId() { return id; }
public void setId(String id) { this.id = id; }
@DynamoDbVersionAttribute
public Long getVersion() { return version; }
public void setVersion(Long version) { this.version = version; }
}
A losing writer gets a ConditionalCheckFailedException and should re-read, re-apply and retry. The extensions docs list three traps:
deleteItemignores the version, so add a condition expression yourself.- Passing your own extension list drops the defaults unless you add
VersionedRecordExtensionback. - Global tables resolve conflicts by last writer wins, so the lock may not hold across Regions.
10. How do you move large files to S3 from Java?
With the S3 Transfer Manager on top of either the CRT-based S3 client or the Java async client with multipart enabled. Either one runs multipart uploads and byte-range downloads in parallel.
You also get progress listeners and pause and resume, per the Transfer Manager docs.
S3AsyncClient s3 = S3AsyncClient.crtBuilder().build();
S3TransferManager tm = S3TransferManager.builder().s3Client(s3).build();
tm.uploadFile(u -> u.source(Paths.get("/data/export.parquet"))
.putObjectRequest(p -> p.bucket(bucket).key("exports/export.parquet")))
.completionFuture()
.join();
S3TransferManager.create() with no aws-crt dependency falls back to the Java client with multipart on. Directory uploads report failures per file in failedTransfers(), which a careful candidate checks instead of assuming success.
Presigned URL downloads, added in SDK 2.48.0, work only with the Java client, not CRT.
Java on Lambda
11. Which handler shapes does the Java runtime accept?
Three. RequestHandler<I, O> gives you built-in JSON mapping. RequestStreamHandler hands you raw streams for your own serializer. A plain method can also take basic types such as Map<String, String>.
Per the Java handler docs, the interface forms name the method handleRequest and use the handler string package.Class. The plain form uses package.Class::method.
public class OrderHandler implements RequestHandler<SQSEvent, Void> {
private static final S3Client S3 = S3Client.create();
@Override
public Void handleRequest(SQSEvent event, Context context) {
for (SQSEvent.SQSMessage msg : event.getRecords()) {
store(msg.getBody());
}
return null;
}
}
Event types for SQS, S3, API Gateway and others come from aws-lambda-java-events. On Java 17 and later, a record works as the input type.
12. How do you package a Java function so it starts faster?
Ship less and unpack less. The same handler docs give three Java-specific rules:
- Depend on single SDK service modules, not the whole SDK.
- Put dependency JARs in a
/libdirectory instead of one uber JAR full of loose classes. Lambda unpacks that layout faster. - Prefer light dependency injection such as Dagger or Guice over the full Spring Framework.
One rule surprises most candidates: avoid the Java DNS cache. Lambda already caches DNS, and a second cache can cause connection timeouts. java.util.logging.Logger can turn on the JVM cache.
So the docs set networkaddress.cache.ttl to 0 in a static block before any logger exists.
13. What does Lambda change in JVM tiered compilation, and when would you override it?
On Java 17 and Java 21, Lambda stops tiered compilation at level 1 (C1 only) by default. That favors short cold starts over peak speed. Java 25 keeps the default, except with SnapStart or provisioned concurrency.
There the normal JVM settings apply, because compiling happens outside the invoke path. Java 11 and older use the JVM defaults, per the Java startup customization docs.
Override it with the JAVA_TOOL_OPTIONS environment variable. Use -XX:+TieredCompilation -XX:TieredStopAtLevel=1 for small, fast functions. Use level 4 for long, CPU-heavy work that can pay for C2 warm-up.
The same variable switches the garbage collector, for example -XX:+UseParallelGC for memory-heavy multi-core functions.
14. How do CRaC runtime hooks and priming work with SnapStart?
Implement org.crac.Resource and register it with Core.getGlobalContext(). Lambda calls beforeCheckpoint() before it snapshots the initialized environment and afterRestore() after it resumes one.
Per the runtime hooks docs, beforeCheckpoint hooks run in reverse registration order and afterRestore hooks in registration order.
public class Handler implements RequestHandler<Map<String, String>, String>, Resource {
private static final DynamoDbClient DDB = DynamoDbClient.create();
private String instanceId;
public Handler() {
Core.getGlobalContext().register(this);
}
@Override
public void beforeCheckpoint(org.crac.Context<? extends Resource> ctx) throws Exception {
primeReadPath(); // touch hot classes so they are loaded and compiled in the snapshot
}
@Override
public void afterRestore(org.crac.Context<? extends Resource> ctx) throws Exception {
instanceId = UUID.randomUUID().toString();
}
@Override
public String handleRequest(Map<String, String> input, Context context) {
return instanceId;
}
}
The registry keeps only a weak reference, so registering new MyResource() inline lets the GC drop the hook silently. Timing matters too. Init plus beforeCheckpoint may run up to 130 seconds or the function timeout, whichever is higher.
The restore and afterRestore must finish within 10 seconds, or Lambda throws SnapStartTimeoutException.

Priming is what you put in beforeCheckpoint. AWS's priming post (April 29, 2025) compares two kinds. Class priming loads classes with Class.forName(). Invoke priming runs real code paths. Invoke priming was faster in its Spring Boot sample.
It is safe only when the primed code is idempotent or does not change state.
15. What breaks uniqueness under SnapStart, and how do you catch it?
Anything unique made during init gets copied into each restored environment. Think of a UUID in a static field, a seeded java.util.Random or a cached token. The uniqueness docs say to create such values in the handler or in afterRestore.
java.security.SecureRandom on the managed Java runtimes stays safe because Lambda reseeds the kernel generator on restore. Bundled crypto libraries need minimum versions: SDK for Java 2.x 2.23.20 on x86 and 2.26.12 on Arm.
AWS also publishes a SpotBugs plugin, aws-lambda-snapstart-java-rules, that flags code assuming uniqueness at init. Network connections opened at init may also be stale, though SDK connections usually resume on their own.
16. When would you compile a function with GraalVM native image?
Native image fits when cold start must be minimal without SnapStart and the team accepts a heavier build. A native image does not run on a JVM, so it cannot use the Java managed runtimes.
You deploy the binary plus a bootstrap file to an OS-only runtime such as provided.al2023. See the Spring Cloud Function AWS docs describe on native functions.
A senior engineer names the trade-off. SnapStart does not support OS-only runtimes, so you pick one or the other. SnapStart keeps the normal JVM and JIT with no build changes.
Native image moves the cost into a longer build and one binary per architecture.
17. How do you run Spring Boot on Lambda?
Two supported paths. Spring Cloud Function exposes Function beans and uses org.springframework.cloud.function.adapter.aws.FunctionInvoker as the handler, which keeps business code free of Lambda types.
The AWS Serverless Java Container runs an existing Spring MVC app behind API Gateway by translating proxy events into servlet requests.
Version pairing matters: Serverless Java Container 3.x targets Spring Boot 4 and Spring Framework 7, while 2.x stays on Boot 3.
Lambda environment variable names cannot contain dots or hyphens, so spring.cloud.function.definition becomes spring_cloud_function_definition. Spring startup cost is why the Spring Cloud Function docs point to SnapStart.
It is also why AWS's handler guidance prefers lighter DI.
18. What does Powertools for AWS Lambda (Java) v2 give you?
Logging, metrics and tracing utilities plus idempotency, batch processing, parameters, large messages and validation, built for Java conventions. Version 2.0.0, released on June 12, 2025, rebuilt logging on SLF4J with Log4j2 or Logback.
Structured keys now go through MDC and StructuredArguments.
The upgrade guide lists what moved. Idempotency and parameters split into per-provider modules to shrink packages. The AspectJ runtime no longer ships by default, and powertools-batch replaced powertools-sqs.
Version 1 reached end of life on December 12, 2025, and since 2.11.0 the library requires Java 17 or later.
JVM on ECS and Fargate
19. How do you size the JVM heap in an ECS or Fargate task?
Set -XX:MaxRAMPercentage instead of a fixed -Xmx. Container support is on by default, so the JVM reads the task's memory limit. But the java command reference sets the default maximum heap at 25% of that limit.
A 4 GB task therefore runs with about 1 GB of heap unless you change it.
JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=75
Leave headroom for metaspace, thread stacks, direct buffers and native libraries such as the CRT client. Without it, the container can exceed its memory limit and be stopped while the heap still looks healthy.
-Xlog:os+container=trace shows what limits the JVM detected. -XX:ActiveProcessorCount overrides the CPU count the JVM uses to size its thread pools.
20. What changes when you move a Java service to Graviton?
Little in the Java code itself; the risk sits in native code. AWS's Graviton getting started guide (its Java page) recommends JDK 17 or newer and Amazon Corretto.
It notes that Corretto 11 and later use LSE atomic instructions, which help lock-contended workloads and garbage collection.
The audit item is JNI. Any JAR that bundles a native library needs an arm64 build, or it fails or falls back to slower pure Java code. JDK 18 and later turn on the Graviton-tuned AES/GCM code that TLS uses.
On Lambda, all supported runtimes run on both x86_64 and arm64. A Java function can switch with a config change once its dependencies pass.
21. Do virtual threads help with SDK calls?
Yes for blocking sync clients, with two limits. Virtual threads let thousands of blocking calls wait at low cost. But JDK 21 pinned a virtual thread to its carrier inside synchronized blocks.
JEP 491 removed that pinning in JDK 24, so the java25 runtime has the fix and java21 does not. Native calls that block can still pin.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String key : keys) {
executor.submit(() -> S3.getObjectAsBytes(r -> r.bucket(BUCKET).key(key)));
}
}
The second limit is the pool. Ten thousand virtual threads still share one client's 50 connections, and callers that cannot get one within 10 seconds fail. Raise maxConnections or bound concurrency with a semaphore.
The SDK docs list virtual thread support on Java 21 among the Apache 5 client's improvements over Apache 4.
22. How do you trace a Java service now that the X-Ray SDK is in maintenance?
With OpenTelemetry, through the AWS Distro for OpenTelemetry (ADOT) Java agent. The X-Ray migration guide put the X-Ray SDKs into maintenance mode on February 25, 2026. The ADOT agent repackages the OpenTelemetry Java agent.
It attaches with -javaagent and instruments popular libraries with no code changes, per its getting started docs.
OTEL_RESOURCE_ATTRIBUTES=service.name=orders,service.namespace=checkout \
java -javaagent:/opt/aws-opentelemetry-agent.jar -jar orders.jar
The flag must come before -jar. The ADOT Java repo dropped support for its 1.x releases on March 16, 2026. Check which agent major version a team runs before trusting its span names.
Spring and Databases
23. How does @SqsListener in Spring Cloud AWS acknowledge messages?
By default it deletes a message only after the listener returns without an exception (ON_SUCCESS). It also batches those deletes: for standard queues, once a second or once 10 messages are waiting.
SQS redelivers a failed message only after its visibility timeout expires, per the Spring Cloud AWS reference.
@SqsListener(queueNames = "orders-queue")
public void onOrder(OrderCreated event) {
orderService.handle(event);
}
Two consequences come up in reviews. A crash inside that one second window redelivers messages that already succeeded, so the listener must be idempotent. And an error handler that swallows exceptions makes failures look like successes.
Each container handles up to 10 messages per queue at once by default. Since 3.4, an ImmediateRetryAsyncErrorHandler can retry without waiting for the visibility timeout.
24. How should a Java service get database credentials on AWS?
From IAM or Secrets Manager, never from a properties file. With IAM database auth, the app signs a token with RdsUtilities and uses it as the password. Signing makes no network call.
Each token lasts 15 minutes and counts only at connect time, per the IAM DB auth docs, so pooled connections outlive it.
RdsUtilities rds = RdsUtilities.builder()
.region(Region.US_EAST_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
String token = rds.generateAuthenticationToken(r -> r
.hostname(host).port(5432).username("app_user"));
The alternatives: the Secrets Manager JDBC driver wraps the real driver, caches credentials and refreshes them hourly and on rotation.
Spring apps can load secrets with spring.config.import=aws-secretsmanager:/secrets/db, which fails startup if the secret is missing unless prefixed with optional:.
Behind RDS Proxy, IAM can cover just client to proxy, or end to end with no database secret at all.
25. What does the AWS Advanced JDBC Wrapper do during an Aurora failover?
It reconnects the app to the new writer in seconds instead of waiting for DNS. The wrapper keeps a cache of the cluster topology, so after a failover it connects straight to the promoted instance.
You enable it with the jdbc:aws-wrapper:postgresql:// (or mysql) prefix, and auroraConnectionTracker,failover2,efm2 are the default plugins, per the wrapper docs.
The app still has work to do, and the SQLState tells it what:
} catch (SQLException e) {
if ("08S02".equals(e.getSQLState())) {
// failed over outside a transaction: reapply session settings, rerun the statement
} else if ("08007".equals(e.getSQLState())) {
// failed over inside a transaction: outcome unknown, restart the whole transaction
} else {
throw e;
}
}
A code of 08001 means failover itself failed. The wrapper's failover and monitoring plugins do not work through RDS Proxy, which already handles topology. Pick one per connection path.
What Changed Recently
26. What does the end of support for SDK for Java 1.x mean for a codebase?
No more updates or releases of any kind after December 31, 2025, including security fixes. The AWS announcement set maintenance mode from July 31, 2024. Old releases stay on Maven Central, so builds keep working.
That is why stragglers go unnoticed.
The practical answer is an inventory. Search the dependency tree for com.amazonaws:aws-java-sdk, including transitive pulls from older libraries. Run the OpenRewrite tool on service client code, then port DynamoDBMapper by hand.
A new service on 1.x today is a red flag in review.
27. What changed when Apache5HttpClient became the default?
From SDK 2.46.0 (June 1, 2026), service clients pull in apache5-client and use Apache5HttpClient when no HTTP client is configured. The API matches the old client, but the release notes list three behavior changes:
- Different logger names.
Expect: 100-Continueis off by default.- TCP keep-alive options need
jdk.net.NetworkPermissionunder a SecurityManager.
A team that set ApacheHttpClient by name keeps the deprecated 4.x client until it changes the builder. Apache HttpClient 4.x itself is in maintenance mode.
A team that upgraded the BOM without reading the notes may find its HTTP wire logging silent.
28. What is different about the java25 Lambda runtime?
It launched on November 14, 2025, built on Amazon Corretto 25, and changes three startup details:
- It ships an ahead-of-time cache (JEP 515) for the runtime interface client.
- It drops the Log4Shell patch that older Java runtimes carry, so apps must use Log4j 2.17.0 or later.
- It uses full tiered compilation with SnapStart or provisioned concurrency.
AWS advises against custom AOT caches on managed runtimes, since a runtime update can break them. Use a container image if you need one.
The runtimes page also lists java8.al2023, java11.al2023 and java17.al2023 for teams that cannot change Java version. The Amazon Linux 2 Java runtimes deprecate on June 30, 2027.
29. What does Spring Boot 4 change for Java on AWS?
The AWS integration libraries moved to new major versions. Spring Cloud AWS 4.0.0 (January 28, 2026) targets Boot 4 and Framework 7. It folds in Spring Integration AWS, with SQS, SNS, S3, DynamoDB and Kinesis adapters.
It also adds @SqsHandler to route several payload types through one listener. The compatibility table keeps 3.4.x as the last line for Boot 3.5.
Serverless Java Container 3.0.0 (February 23, 2026) made the same move and switched to Jackson 3. Upgrade Boot and these libraries together; the 3.x lines are built against Spring Framework 6.
30. How do you test AWS integrations locally with Testcontainers?
With Testcontainers and LocalStack, after two breaking changes. Testcontainers 2.0.0 removed JUnit 4 support and renamed modules. The LocalStack one is now testcontainers-localstack, and container classes moved into per-module packages.
Since March 23, 2026, the localstack/localstack image fails to start without a LOCALSTACK_AUTH_TOKEN, per the LocalStack module docs.
S3Client s3 = S3Client.builder()
.endpointOverride(localstack.getEndpoint())
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())))
.region(Region.of(localstack.getRegion()))
.build();
Pass the token from CI secrets with .withEnv("LOCALSTACK_AUTH_TOKEN", System.getenv("LOCALSTACK_AUTH_TOKEN")). Build test clients from the container's endpoint and keys, as above.
Then a test can never fall through the credentials chain to a real account.
Signs of a Strong Answer
- They name the HTTP client their service uses and why. They know the 50-connection pool limit before tuning anything else.
- They set
apiCallTimeoutand a non-legacy retry strategy on every client without being asked. - They know which Lambda Java runtimes stop at C1 and when SnapStart changes that. They measure before setting
JAVA_TOOL_OPTIONS. - They put unique values in
afterRestoreand keep a strong reference to the CRaC resource. Their invoke priming has no side effects. - They size the heap with
MaxRAMPercentageand can explain a task killed for memory while its heap looked healthy. - They handle SQLState
08S02and08007differently after an Aurora failover.
Hiring Java Developers for AWS
Many strong Java engineers who have shipped SnapStart functions, tuned SDK clients and handled Aurora failovers work remotely from Asia. Second Talent matches companies with pre-vetted Java developers screened with questions like these.
See what one costs on our Java developer cost page.
Tell us the stack and we send a shortlist within 24 hours. Start hiring, or pair this page with our AWS and Spring Boot interview guides.






