or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aws-sdk-integration.mdaws-services.mdcontainer-configuration.mdendpoint-configuration.mdindex.mdnetwork-configuration.md
tile.json

aws-sdk-integration.mddocs/

AWS SDK Integration

This document provides comprehensive examples for integrating LocalStack with AWS SDK v1 and v2 for Java, including error handling, edge cases, and complete working examples.

AWS SDK v2 Integration

AWS SDK v2 is the modern, modular SDK for Java with improved performance and developer experience.

Basic S3 Client

import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.S3Exception;

LocalStackContainer localstack = new LocalStackContainer(
    DockerImageName.parse("localstack/localstack:2.0")
)
    .withServices(Service.S3);

localstack.start();

try {
    S3Client s3 = S3Client.builder()
        .endpointOverride(localstack.getEndpoint())
        .credentialsProvider(
            StaticCredentialsProvider.create(
                AwsBasicCredentials.create(
                    localstack.getAccessKey(),
                    localstack.getSecretKey()
                )
            )
        )
        .region(Region.of(localstack.getRegion()))
        .build();

    // Use the client
    s3.createBucket(b -> b.bucket("test-bucket"));
} catch (S3Exception e) {
    System.err.println("S3 error: " + e.getMessage());
    throw e;
} catch (SdkClientException e) {
    System.err.println("SDK client error: " + e.getMessage());
    throw e;
} finally {
    localstack.stop();
}

Multiple Service Clients

import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

LocalStackContainer localstack = new LocalStackContainer(
    DockerImageName.parse("localstack/localstack:2.0")
)
    .withServices(Service.S3, Service.SQS, Service.DYNAMODB);

localstack.start();

try {
    // Shared configuration
    StaticCredentialsProvider credentialsProvider =
        StaticCredentialsProvider.create(
            AwsBasicCredentials.create(
                localstack.getAccessKey(),
                localstack.getSecretKey()
            )
        );

    Region region = Region.of(localstack.getRegion());
    URI endpoint = localstack.getEndpoint();

    // Create S3 client
    S3Client s3 = S3Client.builder()
        .endpointOverride(endpoint)
        .credentialsProvider(credentialsProvider)
        .region(region)
        .build();

    // Create SQS client
    SqsClient sqs = SqsClient.builder()
        .endpointOverride(endpoint)
        .credentialsProvider(credentialsProvider)
        .region(region)
        .build();

    // Create DynamoDB client
    DynamoDbClient dynamodb = DynamoDbClient.builder()
        .endpointOverride(endpoint)
        .credentialsProvider(credentialsProvider)
        .region(region)
        .build();

    // Use the clients
    s3.createBucket(b -> b.bucket("test-bucket"));
    sqs.createQueue(q -> q.queueName("test-queue"));
} finally {
    localstack.stop();
}

S3 Operations with AWS SDK v2

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.model.S3Exception;

S3Client s3 = // ... configured as above

try {
    // Create bucket
    s3.createBucket(b -> b.bucket("my-bucket"));

    // Put object
    s3.putObject(
        p -> p.bucket("my-bucket").key("document.txt"),
        RequestBody.fromString("Hello from LocalStack!")
    );

    // Get object
    String content = s3.getObject(
        g -> g.bucket("my-bucket").key("document.txt"),
        ResponseTransformer.toBytes()
    ).asUtf8String();

    // List buckets
    ListBucketsResponse bucketsResponse = s3.listBuckets();
    bucketsResponse.buckets().forEach(bucket -> {
        System.out.println("Bucket: " + bucket.name());
    });

    // List objects
    ListObjectsV2Response objectsResponse = s3.listObjectsV2(
        l -> l.bucket("my-bucket")
    );
    objectsResponse.contents().forEach(object -> {
        System.out.println("Object: " + object.key());
    });

    // Delete object
    s3.deleteObject(d -> d.bucket("my-bucket").key("document.txt"));

    // Delete bucket (must be empty)
    s3.deleteBucket(d -> d.bucket("my-bucket"));
} catch (NoSuchBucketException e) {
    System.err.println("Bucket does not exist: " + e.getMessage());
} catch (NoSuchKeyException e) {
    System.err.println("Object does not exist: " + e.getMessage());
} catch (S3Exception e) {
    System.err.println("S3 error: " + e.awsErrorDetails().errorMessage());
    throw e;
} catch (SdkClientException e) {
    System.err.println("SDK client error: " + e.getMessage());
    throw e;
}

S3 Path-Style vs Virtual-Hosted Style

LocalStack requires path-style access for S3. The getEndpoint() method returns an IP address to ensure path-style access works correctly:

import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

LocalStackContainer localstack = // ... configured

localstack.start();

URI endpoint = localstack.getEndpoint();
// Returns IP address like: http://192.168.1.100:49153
// NOT hostname like: http://localhost:49153

S3Client s3 = S3Client.builder()
    .endpointOverride(endpoint)  // IP address ensures path-style access
    .forcePathStyle(true)  // Explicitly enable path-style (recommended)
    .credentialsProvider(/* ... */)
    .region(Region.of(localstack.getRegion()))
    .build();

// This works correctly with path-style access
s3.putObject(
    PutObjectRequest.builder()
        .bucket("my-bucket")
        .key("my-key")
        .build(),
    RequestBody.fromString("content")
);

SQS Operations with AWS SDK v2

import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import software.amazon.awssdk.core.exception.SdkClientException;

SqsClient sqs = // ... configured as above

try {
    // Create queue
    CreateQueueResponse createResponse = sqs.createQueue(
        c -> c.queueName("my-queue")
    );
    String queueUrl = createResponse.queueUrl();

    // Send message
    sqs.sendMessage(m -> m
        .queueUrl(queueUrl)
        .messageBody("Test message")
    );

    // Receive messages
    ReceiveMessageResponse receiveResponse = sqs.receiveMessage(
        r -> r.queueUrl(queueUrl).maxNumberOfMessages(10)
    );

    receiveResponse.messages().forEach(message -> {
        System.out.println("Message: " + message.body());

        // Delete message after processing
        sqs.deleteMessage(d -> d
            .queueUrl(queueUrl)
            .receiptHandle(message.receiptHandle())
        );
    });

    // Delete queue
    sqs.deleteQueue(d -> d.queueUrl(queueUrl));
} catch (QueueDoesNotExistException e) {
    System.err.println("Queue does not exist: " + e.getMessage());
} catch (SdkClientException e) {
    System.err.println("SDK client error: " + e.getMessage());
    throw e;
}

DynamoDB Operations with AWS SDK v2

import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.awssdk.core.exception.SdkClientException;
import java.util.Map;

DynamoDbClient dynamodb = // ... configured as above

try {
    // Create table
    dynamodb.createTable(CreateTableRequest.builder()
        .tableName("Users")
        .keySchema(
            KeySchemaElement.builder()
                .attributeName("userId")
                .keyType(KeyType.HASH)
                .build()
        )
        .attributeDefinitions(
            AttributeDefinition.builder()
                .attributeName("userId")
                .attributeType(ScalarAttributeType.S)
                .build()
        )
        .billingMode(BillingMode.PAY_PER_REQUEST)
        .build()
    );

    // Wait for table to be active (optional but recommended)
    dynamodb.waiter().waitUntilTableExists(
        b -> b.tableName("Users")
    );

    // Put item
    dynamodb.putItem(PutItemRequest.builder()
        .tableName("Users")
        .item(Map.of(
            "userId", AttributeValue.builder().s("user123").build(),
            "name", AttributeValue.builder().s("John Doe").build(),
            "email", AttributeValue.builder().s("john@example.com").build()
        ))
        .build()
    );

    // Get item
    GetItemResponse getResponse = dynamodb.getItem(GetItemRequest.builder()
        .tableName("Users")
        .key(Map.of("userId", AttributeValue.builder().s("user123").build()))
        .build()
    );

    if (getResponse.hasItem()) {
        Map<String, AttributeValue> item = getResponse.item();
        String name = item.get("name").s();
        System.out.println("User name: " + name);
    }

    // Scan table
    ScanResponse scanResponse = dynamodb.scan(
        s -> s.tableName("Users")
    );

    scanResponse.items().forEach(userItem -> {
        System.out.println("User: " + userItem.get("userId").s());
    });
} catch (ResourceInUseException e) {
    System.err.println("Table already exists: " + e.getMessage());
} catch (ResourceNotFoundException e) {
    System.err.println("Table does not exist: " + e.getMessage());
} catch (SdkClientException e) {
    System.err.println("SDK client error: " + e.getMessage());
    throw e;
}

Lambda Operations with AWS SDK v2

import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.*;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.exception.SdkClientException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

LambdaClient lambda = // ... configured as above

try {
    // Create function (requires zip file)
    byte[] zipBytes = Files.readAllBytes(Paths.get("function.zip"));
    
    lambda.createFunction(CreateFunctionRequest.builder()
        .functionName("my-function")
        .runtime(Runtime.PYTHON3_11)
        .handler("index.handler")
        .role("arn:aws:iam::000000000000:role/lambda-role")
        .code(FunctionCode.builder()
            .zipFile(SdkBytes.fromByteArray(zipBytes))
            .build())
        .build()
    );

    // Wait for function to be active
    lambda.waiter().waitUntilFunctionActive(
        b -> b.functionName("my-function")
    );

    // Invoke function
    InvokeResponse invokeResponse = lambda.invoke(InvokeRequest.builder()
        .functionName("my-function")
        .payload(SdkBytes.fromUtf8String("{\"key\": \"value\"}"))
        .build()
    );

    String result = invokeResponse.payload().asUtf8String();
    System.out.println("Lambda result: " + result);
} catch (ResourceConflictException e) {
    System.err.println("Function already exists: " + e.getMessage());
} catch (ResourceNotFoundException e) {
    System.err.println("Function does not exist: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Failed to read function zip: " + e.getMessage());
    throw e;
} catch (SdkClientException e) {
    System.err.println("SDK client error: " + e.getMessage());
    throw e;
}

AWS SDK v1 Integration

AWS SDK v1 is the legacy SDK, still widely used in existing projects.

Basic S3 Client

import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
import org.testcontainers.utility.DockerImageName;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.AmazonClientException;
import com.amazonaws.services.s3.model.AmazonS3Exception;

LocalStackContainer localstack = new LocalStackContainer(
    DockerImageName.parse("localstack/localstack:2.0")
)
    .withServices(Service.S3);

localstack.start();

try {
    AmazonS3 s3 = AmazonS3ClientBuilder.standard()
        .withEndpointConfiguration(
            new AwsClientBuilder.EndpointConfiguration(
                localstack.getEndpoint().toString(),
                localstack.getRegion()
            )
        )
        .withCredentials(
            new AWSStaticCredentialsProvider(
                new BasicAWSCredentials(
                    localstack.getAccessKey(),
                    localstack.getSecretKey()
                )
            )
        )
        .withPathStyleAccessEnabled(true)  // Required for LocalStack
        .build();

    // Use the client
    s3.createBucket("test-bucket");
} catch (AmazonS3Exception e) {
    System.err.println("S3 error: " + e.getMessage());
    throw e;
} catch (AmazonClientException e) {
    System.err.println("AWS client error: " + e.getMessage());
    throw e;
} finally {
    localstack.stop();
}

Multiple Service Clients with SDK v1

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

LocalStackContainer localstack = new LocalStackContainer(
    DockerImageName.parse("localstack/localstack:2.0")
)
    .withServices(Service.S3, Service.SQS, Service.DYNAMODB);

localstack.start();

try {
    // Shared configuration
    AwsClientBuilder.EndpointConfiguration endpointConfig =
        new AwsClientBuilder.EndpointConfiguration(
            localstack.getEndpoint().toString(),
            localstack.getRegion()
        );

    AWSStaticCredentialsProvider credentialsProvider =
        new AWSStaticCredentialsProvider(
            new BasicAWSCredentials(
                localstack.getAccessKey(),
                localstack.getSecretKey()
            )
        );

    // Create S3 client
    AmazonS3 s3 = AmazonS3ClientBuilder.standard()
        .withEndpointConfiguration(endpointConfig)
        .withCredentials(credentialsProvider)
        .withPathStyleAccessEnabled(true)
        .build();

    // Create SQS client
    AmazonSQS sqs = AmazonSQSClientBuilder.standard()
        .withEndpointConfiguration(endpointConfig)
        .withCredentials(credentialsProvider)
        .build();

    // Create DynamoDB client
    AmazonDynamoDB dynamodb = AmazonDynamoDBClientBuilder.standard()
        .withEndpointConfiguration(endpointConfig)
        .withCredentials(credentialsProvider)
        .build();

    // Use the clients
    s3.createBucket("test-bucket");
    sqs.createQueue("test-queue");
} finally {
    localstack.stop();
}

S3 Operations with AWS SDK v1

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.AmazonClientException;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import org.apache.commons.io.IOUtils;
import java.nio.charset.StandardCharsets;

AmazonS3 s3 = // ... configured as above

try {
    // Create bucket
    s3.createBucket("my-bucket");

    // Put object
    s3.putObject("my-bucket", "document.txt", "Hello from LocalStack!");

    // Get object
    S3Object object = s3.getObject("my-bucket", "document.txt");
    String content = IOUtils.toString(
        object.getObjectContent(),
        StandardCharsets.UTF_8
    );

    // List buckets
    List<Bucket> buckets = s3.listBuckets();
    buckets.forEach(bucket -> {
        System.out.println("Bucket: " + bucket.getName());
    });

    // List objects
    ObjectListing objectListing = s3.listObjects("my-bucket");
    objectListing.getObjectSummaries().forEach(summary -> {
        System.out.println("Object: " + summary.getKey());
    });

    // Delete object
    s3.deleteObject("my-bucket", "document.txt");

    // Delete bucket (must be empty)
    s3.deleteBucket("my-bucket");
} catch (AmazonS3Exception e) {
    if (e.getStatusCode() == 404) {
        System.err.println("Bucket or object not found: " + e.getMessage());
    } else {
        System.err.println("S3 error: " + e.getMessage());
        throw e;
    }
} catch (AmazonClientException e) {
    System.err.println("AWS client error: " + e.getMessage());
    throw e;
}

SQS Operations with AWS SDK v1

import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.*;
import com.amazonaws.AmazonClientException;

AmazonSQS sqs = // ... configured as above

try {
    // Create queue
    CreateQueueResult createResult = sqs.createQueue("my-queue");
    String queueUrl = createResult.getQueueUrl();

    // Send message
    sqs.sendMessage(queueUrl, "Test message");

    // Receive messages
    ReceiveMessageResult receiveResult = sqs.receiveMessage(
        new ReceiveMessageRequest(queueUrl)
            .withMaxNumberOfMessages(10)
    );

    receiveResult.getMessages().forEach(message -> {
        System.out.println("Message: " + message.getBody());

        // Delete message after processing
        sqs.deleteMessage(queueUrl, message.getReceiptHandle());
    });

    // Delete queue
    sqs.deleteQueue(queueUrl);
} catch (QueueDoesNotExistException e) {
    System.err.println("Queue does not exist: " + e.getMessage());
} catch (AmazonClientException e) {
    System.err.println("AWS client error: " + e.getMessage());
    throw e;
}

DynamoDB Operations with AWS SDK v1

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.model.*;
import com.amazonaws.AmazonClientException;
import java.util.HashMap;
import java.util.Map;

AmazonDynamoDB dynamodb = // ... configured as above

try {
    // Create table
    CreateTableRequest createTableRequest = new CreateTableRequest()
        .withTableName("Users")
        .withKeySchema(new KeySchemaElement("userId", KeyType.HASH))
        .withAttributeDefinitions(
            new AttributeDefinition("userId", ScalarAttributeType.S)
        )
        .withBillingMode(BillingMode.PAY_PER_REQUEST);

    dynamodb.createTable(createTableRequest);

    // Wait for table to be active
    TableDescription table = dynamodb.describeTable("Users").getTable();
    while (!"ACTIVE".equals(table.getTableStatus())) {
        Thread.sleep(1000);
        table = dynamodb.describeTable("Users").getTable();
    }

    // Put item
    Map<String, AttributeValue> item = new HashMap<>();
    item.put("userId", new AttributeValue("user123"));
    item.put("name", new AttributeValue("John Doe"));
    item.put("email", new AttributeValue("john@example.com"));

    dynamodb.putItem("Users", item);

    // Get item
    Map<String, AttributeValue> key = new HashMap<>();
    key.put("userId", new AttributeValue("user123"));

    GetItemResult getResult = dynamodb.getItem("Users", key);
    if (getResult.getItem() != null) {
        Map<String, AttributeValue> resultItem = getResult.getItem();
        String name = resultItem.get("name").getS();
        System.out.println("User name: " + name);
    }
} catch (ResourceInUseException e) {
    System.err.println("Table already exists: " + e.getMessage());
} catch (ResourceNotFoundException e) {
    System.err.println("Table does not exist: " + e.getMessage());
} catch (AmazonClientException e) {
    System.err.println("AWS client error: " + e.getMessage());
    throw e;
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RuntimeException("Interrupted while waiting for table", e);
}

JUnit Integration

JUnit 4

import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

public class AwsIntegrationTest {

    @ClassRule
    public static LocalStackContainer localstack = new LocalStackContainer(
        DockerImageName.parse("localstack/localstack:2.0")
    )
        .withServices(Service.S3, Service.SQS);

    @Test
    public void testS3Operations() {
        S3Client s3 = S3Client.builder()
            .endpointOverride(localstack.getEndpoint())
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        localstack.getAccessKey(),
                        localstack.getSecretKey()
                    )
                )
            )
            .region(Region.of(localstack.getRegion()))
            .build();

        // Test code
        s3.createBucket(b -> b.bucket("test-bucket"));
        // ... assertions
    }
}

JUnit 5

import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.localstack.LocalStackContainer.Service;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Testcontainers
public class AwsIntegrationTest {

    @Container
    static LocalStackContainer localstack = new LocalStackContainer(
        DockerImageName.parse("localstack/localstack:2.0")
    )
        .withServices(Service.S3, Service.SQS);

    @Test
    void testS3Operations() {
        S3Client s3 = S3Client.builder()
            .endpointOverride(localstack.getEndpoint())
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        localstack.getAccessKey(),
                        localstack.getSecretKey()
                    )
                )
            )
            .region(Region.of(localstack.getRegion()))
            .build();

        // Test code
        s3.createBucket(b -> b.bucket("test-bucket"));
        // ... assertions
    }
}

Helper Method Pattern

Create a reusable helper method to configure clients:

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import org.testcontainers.containers.localstack.LocalStackContainer;

public class LocalStackClientHelper {

    public static S3Client createS3Client(LocalStackContainer localstack) {
        return S3Client.builder()
            .endpointOverride(localstack.getEndpoint())
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        localstack.getAccessKey(),
                        localstack.getSecretKey()
                    )
                )
            )
            .region(Region.of(localstack.getRegion()))
            .forcePathStyle(true)
            .build();
    }

    public static SqsClient createSqsClient(LocalStackContainer localstack) {
        return SqsClient.builder()
            .endpointOverride(localstack.getEndpoint())
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        localstack.getAccessKey(),
                        localstack.getSecretKey()
                    )
                )
            )
            .region(Region.of(localstack.getRegion()))
            .build();
    }

    public static DynamoDbClient createDynamoDbClient(LocalStackContainer localstack) {
        return DynamoDbClient.builder()
            .endpointOverride(localstack.getEndpoint())
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        localstack.getAccessKey(),
                        localstack.getSecretKey()
                    )
                )
            )
            .region(Region.of(localstack.getRegion()))
            .build();
    }
}

// Usage
S3Client s3 = LocalStackClientHelper.createS3Client(localstack);
SqsClient sqs = LocalStackClientHelper.createSqsClient(localstack);

Error Handling Patterns

Comprehensive Error Handling

import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;

public void handleS3Operations(S3Client s3, String bucketName, String key) {
    try {
        // Create bucket
        s3.createBucket(b -> b.bucket(bucketName));
    } catch (BucketAlreadyExistsException e) {
        System.out.println("Bucket already exists, continuing...");
    } catch (BucketAlreadyOwnedByYouException e) {
        System.out.println("Bucket already owned by you, continuing...");
    } catch (S3Exception e) {
        System.err.println("S3 error creating bucket: " + e.awsErrorDetails().errorMessage());
        throw e;
    } catch (SdkClientException e) {
        System.err.println("SDK client error: " + e.getMessage());
        throw e;
    }

    try {
        // Put object
        s3.putObject(
            p -> p.bucket(bucketName).key(key),
            RequestBody.fromString("content")
        );
    } catch (NoSuchBucketException e) {
        System.err.println("Bucket does not exist: " + bucketName);
        throw e;
    } catch (S3Exception e) {
        System.err.println("S3 error putting object: " + e.awsErrorDetails().errorMessage());
        throw e;
    } catch (SdkClientException e) {
        System.err.println("SDK client error: " + e.getMessage());
        throw e;
    }
}

Maven Dependencies

AWS SDK v2

<dependencies>
    <!-- Testcontainers LocalStack -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>localstack</artifactId>
        <version>1.21.4</version>
        <scope>test</scope>
    </dependency>

    <!-- AWS SDK v2 - S3 -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3</artifactId>
        <version>2.28.6</version>
        <scope>test</scope>
    </dependency>

    <!-- AWS SDK v2 - SQS -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>sqs</artifactId>
        <version>2.28.6</version>
        <scope>test</scope>
    </dependency>

    <!-- AWS SDK v2 - DynamoDB -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>dynamodb</artifactId>
        <version>2.28.6</version>
        <scope>test</scope>
    </dependency>
</dependencies>

AWS SDK v1

<dependencies>
    <!-- Testcontainers LocalStack -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>localstack</artifactId>
        <version>1.21.4</version>
        <scope>test</scope>
    </dependency>

    <!-- AWS SDK v1 BOM -->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-bom</artifactId>
        <version>1.12.572</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

    <!-- AWS SDK v1 - S3 -->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-s3</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- AWS SDK v1 - SQS -->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-sqs</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>