Introduction
The AWS SDK for Java v2 is the major evolution for scalable Java apps in 2026. Unlike the synchronous v1, v2 emphasizes asynchronous clients (Netty-based), native pagination, and cross-service transactions—essential for high-performance microservices. This advanced tutorial targets senior developers: we cover Maven setup, secure credentials via STS, S3 multipart uploads >5GB, atomic DynamoDB transactions, EC2 pagination with paginators, and retryable error handling. Every example is a complete, testable snippet ready in <5 minutes with a free AWS account. You'll bookmark this guide for your production deployments. (132 words)
Prerequisites
- Java 17+ (LTS recommended for GraalVM compatibility)
- Maven 3.9+ or Gradle 8+
- AWS account with IAM user (API keys)
- AWS CLI installed for testing (
aws configure) - IDE like IntelliJ with AWS Toolkit
- Advanced Java knowledge (CompletableFuture, Streams)
Maven Configuration (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>aws-sdk-advanced</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<aws.sdk2.version>2.26.20</aws.sdk2.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ec2</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
</project>This pom.xml uses the BOM to manage consistent SDK v2 versions and avoid conflicts. It includes S3, DynamoDB, EC2, and STS for assume-role. Compile with mvn compile; pitfall: don't forget the BOM, or you'll get version mismatches.
Advanced Credentials Management
For production, avoid hardcoding keys. Use ~/.aws/credentials with MFA or assume-role via STS for temporary sessions (15min-12h). The SDK's provider chain prioritizes: env vars > shared creds > IAM roles (EC2/Lambda).
Asynchronous STS AssumeRole Client
package com.example;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsAsyncClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.Credentials;
import java.time.Duration;
public class StsAssumeRole {
public static void main(String[] args) {
StsAsyncClient stsClient = StsAsyncClient.builder()
.region(Region.EU_WEST_1)
.credentialsProvider(ProfileCredentialsProvider.create("dev-profile"))
.build();
AssumeRoleRequest request = AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::123456789012:role/MyCrossAccountRole")
.roleSessionName("java-sdk-session")
.durationSeconds(3600)
.build();
stsClient.assumeRole(request).whenComplete((response, throwable) -> {
if (throwable != null) {
throwable.printStackTrace();
return;
}
Credentials creds = response.credentials();
System.out.println("AccessKey: " + creds.accessKeyId());
System.out.println("Expires: " + creds.expirationTime());
}).join();
stsClient.close();
}
}This code asynchronously assumes a cross-account role using CompletableFuture. Use these temporary creds for other clients. Pitfall: STS region must match; test first with aws sts assume-role.
S3: Advanced Multipart Upload
Analogy: Like splitting a truck into trailers for a congested highway. For files >5GB, multipart is required (100 parts max, 5MB-5GB per part). We build a full upload with metadata and tagging.
Complete S3 Multipart Upload
package com.example;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class S3MultipartUpload {
public static void main(String[] args) throws IOException {
S3Client s3 = S3Client.builder().region(Region.EU_WEST_1).build();
String bucket = "my-advanced-bucket";
String key = "large-file.zip";
File file = new File("path/to/largefile.zip"); // >5GB
// Create multipart upload
CreateMultipartUploadResponse createRes = s3.createMultipartUpload(CreateMultipartUploadRequest.builder()
.bucket(bucket)
.key(key)
.metadata(m -> m.put("app", "java-sdk-v2"))
.tagging("env=prod&priority=high")
.build());
String uploadId = createRes.uploadId();
List<CompletedPart> parts = new ArrayList<>();
int partNumber = 1;
long fileSize = file.length();
long partSize = 5 * 1024 * 1024; // 5MB
byte[] fileContent = Files.readAllBytes(file.toPath());
for (long pos = 0; pos < fileSize; pos += partSize) {
long size = Math.min(partSize, fileSize - pos);
UploadPartResponse partRes = s3.uploadPart(UploadPartRequest.builder()
.bucket(bucket)
.key(key)
.uploadId(uploadId)
.partNumber(partNumber)
.contentLength(size)
.build(), RequestBody.fromBytes(fileContent, (int)pos, (int)size));
parts.add(CompletedPart.builder().eTag(partRes.eTag()).partNumber(partNumber).build());
partNumber++;
}
// Complete
s3.completeMultipartUpload(CompletedMultipartUploadRequest.builder()
.bucket(bucket)
.key(key)
.uploadId(uploadId)
.parts(parts)
.build());
s3.close();
System.out.println("Upload complet!");
}
}Synchronous code for simplicity, but switch to S3AsyncClient for production. Handles metadata/tags automatically. Pitfall: sort parts by ascending partNumber; abort with AbortMultipartUpload on failure.
DynamoDB: Atomic Transactions
TransactWriteItems ensure ACID across tables (100 ops max). Perfect for e-commerce (deduct inventory + create order). Use conditions to avoid race conditions.
DynamoDB TransactWriteItems
package com.example;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.util.Arrays;
public class DynamoDBTransactions {
public static void main(String[] args) {
DynamoDbClient dynamoDb = DynamoDbClient.builder().region(Region.EU_WEST_1).build();
String tableOrders = "Orders";
String tableInventory = "Inventory";
TransactWriteItem orderItem = TransactWriteItem.builder()
.put(Put.builder().tableName(tableOrders)
.item(m -> m.attribute("orderId", AttributeValue.builder().s("ORD-123").build())
.attribute("productId", AttributeValue.builder().s("PROD-456").build())
.attribute("quantity", AttributeValue.builder().n("2").build()))
.build())
.build();
TransactWriteItem inventoryItem = TransactWriteItem.builder()
.update(Update.builder().tableName(tableInventory)
.key(k -> k.attribute("productId", AttributeValue.builder().s("PROD-456").build()))
.updateExpression("ADD quantity :dec")
.expressionAttributeValues(m -> m.put(":dec", AttributeValue.builder().n("2").build()))
.conditionExpression("quantity >= :dec")
.build())
.build();
try {
dynamoDb.transactWriteItems(TransactWriteItemsRequest.builder()
.transactItems(Arrays.asList(orderItem, inventoryItem))
.build());
System.out.println("Transaction réussie!");
} catch (TransactionCanceledException e) {
System.out.println("Annulée: " + e.cancellationReasons());
}
dynamoDb.close();
}
}Atomic transaction: create order + update inventory with condition. If insufficient stock, everything rolls back. Pitfall: limit to 4MB total; use Batch for non-atomic ops.
EC2 Pagination with Paginators
Advanced: For >1000 instances, use paginator() to iterate without manual NextToken. Reactive streams compatible with Reactor/WebFlux.
Advanced EC2 Pagination
package com.example;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
import software.amazon.awssdk.services.ec2.paginators.DescribeInstancesPublisher;
import java.util.concurrent.CompletableFuture;
public class EC2Pagination {
public static void main(String[] args) {
Ec2Client ec2 = Ec2Client.builder().region(Region.EU_WEST_1).build();
DescribeInstancesPublisher publisher = ec2.paginator(DescribeInstancesRequest.builder().maxResults(50).build());
CompletableFuture<Void> future = publisher.subscribe(res -> {
res.reservations().forEach(reservation ->
reservation.instances().forEach(instance ->
System.out.println("Instance: " + instance.instanceId())));
});
future.join();
ec2.close();
}
}Publisher automatically iterates pages (Publisher/Subscriber pattern). Async by default. Pitfall: too-high maxResults causes throttling; filter with .filter() on Flux if using Spring.
Best Practices
- Singleton clients: Reuse one client per region/app with builder.cache()
- Async everywhere: Prefer AsyncClient for non-blocking I/O (>10x perf)
- Retry policy: Configure SdkDefaultClientExceptionRetryClassifier for 503/429
- Structured logging: Enable AWS_XRAY for distributed traces
- Multi-region: Use dynamic Region.of() per service
Common Errors to Avoid
- Incomplete credentials chain: Check
AWS_PROFILEand~/.aws/config; test withaws sts get-caller-identity - Manual pagination: Forgetting NextToken truncates results; always use paginator()
- Multipart without complete: Orphaned storage costs; wrap in try-finally
- ConditionCheck fail: TransactionCanceledException without logging reasons()
Next Steps
- Official docs: AWS SDK Java v2
- GitHub examples: awsdocs/aws-doc-sdk-examples
- Advanced training: Learni Group AWS Training
- Migrate to v3 preview for GraalVM native