DynamoDB with AWS SDK in Java

Overview

Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. The AWS SDK for Java provides comprehensive support for DynamoDB operations.

Setup and Dependencies

Maven Dependencies

<properties>
<aws.java.sdk.version>2.20.0</aws.java.sdk.version>
</properties>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<!-- For async operations -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced-async</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
</dependencies>

Configuration and Client Setup

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.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.net.URI;
public class DynamoDBConfig {
// Synchronous client
public static DynamoDbClient createSyncClient() {
return DynamoDbClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("accessKey", "secretKey")
))
// For local development with DynamoDB Local
.endpointOverride(URI.create("http://localhost:8000"))
.build();
}
// Asynchronous client
public static DynamoDbAsyncClient createAsyncClient() {
return DynamoDbAsyncClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("accessKey", "secretKey")
))
.build();
}
// Enhanced client for object persistence
public static DynamoDbEnhancedClient createEnhancedClient(DynamoDbClient client) {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(client)
.build();
}
}

Basic Table Operations

Example 1: Table Management

public class TableManager {
private final DynamoDbClient dynamoDbClient;
public TableManager(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
public void createUserTable() {
CreateTableRequest request = CreateTableRequest.builder()
.tableName("Users")
.keySchema(
KeySchemaElement.builder()
.attributeName("userId")
.keyType(KeyType.HASH)  // Partition key
.build(),
KeySchemaElement.builder()
.attributeName("email")
.keyType(KeyType.RANGE) // Sort key
.build()
)
.attributeDefinitions(
AttributeDefinition.builder()
.attributeName("userId")
.attributeType(ScalarAttributeType.S)
.build(),
AttributeDefinition.builder()
.attributeName("email")
.attributeType(ScalarAttributeType.S)
.build()
)
.billingMode(BillingMode.PAY_PER_REQUEST)
.provisionedThroughput(ProvisionedThroughput.builder()
.readCapacityUnits(10L)
.writeCapacityUnits(10L)
.build())
.build();
try {
CreateTableResponse response = dynamoDbClient.createTable(request);
System.out.println("Table created: " + response.tableDescription().tableName());
} catch (ResourceInUseException e) {
System.out.println("Table already exists");
}
}
public void describeTable(String tableName) {
DescribeTableRequest request = DescribeTableRequest.builder()
.tableName(tableName)
.build();
DescribeTableResponse response = dynamoDbClient.describeTable(request);
TableDescription table = response.table();
System.out.println("Table: " + table.tableName());
System.out.println("Status: " + table.tableStatus());
System.out.println("Item count: " + table.itemCount());
}
public void listTables() {
ListTablesRequest request = ListTablesRequest.builder()
.limit(10)
.build();
ListTablesResponse response = dynamoDbClient.listTables(request);
response.tableNames().forEach(System.out::println);
}
public void deleteTable(String tableName) {
DeleteTableRequest request = DeleteTableRequest.builder()
.tableName(tableName)
.build();
dynamoDbClient.deleteTable(request);
System.out.println("Table deleted: " + tableName);
}
}

CRUD Operations

Example 2: Basic CRUD Operations

public class UserCRUDOperations {
private final DynamoDbClient dynamoDbClient;
private final String tableName = "Users";
public UserCRUDOperations(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
// Create/Update user
public void putUser(String userId, String email, String name, int age) {
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(userId).build());
item.put("email", AttributeValue.builder().s(email).build());
item.put("name", AttributeValue.builder().s(name).build());
item.put("age", AttributeValue.builder().n(String.valueOf(age)).build());
item.put("createdAt", AttributeValue.builder().s(java.time.Instant.now().toString()).build());
PutItemRequest request = PutItemRequest.builder()
.tableName(tableName)
.item(item)
.build();
dynamoDbClient.putItem(request);
System.out.println("User created/updated: " + userId);
}
// Get user by primary key
public Map<String, AttributeValue> getUser(String userId, String email) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userId).build());
key.put("email", AttributeValue.builder().s(email).build());
GetItemRequest request = GetItemRequest.builder()
.tableName(tableName)
.key(key)
.build();
GetItemResponse response = dynamoDbClient.getItem(request);
return response.item();
}
// Update user attributes
public void updateUserAge(String userId, String email, int newAge) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userId).build());
key.put("email", AttributeValue.builder().s(email).build());
Map<String, AttributeValueUpdate> updates = new HashMap<>();
updates.put("age", AttributeValueUpdate.builder()
.value(AttributeValue.builder().n(String.valueOf(newAge)).build())
.action(AttributeAction.PUT)
.build());
updates.put("updatedAt", AttributeValueUpdate.builder()
.value(AttributeValue.builder().s(java.time.Instant.now().toString()).build())
.action(AttributeAction.PUT)
.build());
UpdateItemRequest request = UpdateItemRequest.builder()
.tableName(tableName)
.key(key)
.attributeUpdates(updates)
.build();
dynamoDbClient.updateItem(request);
System.out.println("User updated: " + userId);
}
// Delete user
public void deleteUser(String userId, String email) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userId).build());
key.put("email", AttributeValue.builder().s(email).build());
DeleteItemRequest request = DeleteItemRequest.builder()
.tableName(tableName)
.key(key)
.build();
dynamoDbClient.deleteItem(request);
System.out.println("User deleted: " + userId);
}
// Print user details
public void printUser(Map<String, AttributeValue> user) {
if (user.isEmpty()) {
System.out.println("User not found");
return;
}
System.out.println("User ID: " + user.get("userId").s());
System.out.println("Email: " + user.get("email").s());
System.out.println("Name: " + user.get("name").s());
System.out.println("Age: " + user.get("age").n());
if (user.containsKey("createdAt")) {
System.out.println("Created: " + user.get("createdAt").s());
}
}
}

Enhanced Client with Object Persistence

Example 3: Enhanced Client with Annotations

import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
@DynamoDbBean
public class User {
private String userId;
private String email;
private String name;
private Integer age;
private String createdAt;
private String updatedAt;
@DynamoDbPartitionKey
@DynamoDbAttribute("userId")
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
@DynamoDbSortKey
@DynamoDbAttribute("email")
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@DynamoDbAttribute("name")
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@DynamoDbAttribute("age")
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
@DynamoDbAttribute("createdAt")
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
@DynamoDbAttribute("updatedAt")
public String getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; }
}
public class EnhancedUserRepository {
private final DynamoDbTable<User> userTable;
public EnhancedUserRepository(DynamoDbEnhancedClient enhancedClient) {
this.userTable = enhancedClient.table("Users", TableSchema.fromBean(User.class));
}
public void createUser(User user) {
user.setCreatedAt(java.time.Instant.now().toString());
userTable.putItem(user);
System.out.println("User created: " + user.getUserId());
}
public User getUser(String userId, String email) {
Key key = Key.builder()
.partitionValue(userId)
.sortValue(email)
.build();
return userTable.getItem(key);
}
public void updateUser(User user) {
user.setUpdatedAt(java.time.Instant.now().toString());
userTable.updateItem(user);
System.out.println("User updated: " + user.getUserId());
}
public void deleteUser(String userId, String email) {
Key key = Key.builder()
.partitionValue(userId)
.sortValue(email)
.build();
userTable.deleteItem(key);
System.out.println("User deleted: " + userId);
}
// Batch operations
public void batchWriteUsers(List<User> users) {
List<User> usersToPut = users.stream()
.peek(user -> user.setCreatedAt(java.time.Instant.now().toString()))
.collect(Collectors.toList());
userTable.putItem(usersToPut);
System.out.println("Batch written: " + users.size() + " users");
}
}

Query and Scan Operations

Example 4: Query and Scan Examples

public class QueryScanOperations {
private final DynamoDbClient dynamoDbClient;
private final String tableName = "Users";
public QueryScanOperations(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
// Query by partition key
public void queryUsersByUserId(String userId) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":userId", AttributeValue.builder().s(userId).build());
QueryRequest request = QueryRequest.builder()
.tableName(tableName)
.keyConditionExpression("userId = :userId")
.expressionAttributeValues(expressionValues)
.build();
QueryResponse response = dynamoDbClient.query(request);
System.out.println("Found " + response.count() + " users");
response.items().forEach(this::printUser);
}
// Query with filter expression
public void queryUsersByUserIdWithFilter(String userId, int minAge) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":userId", AttributeValue.builder().s(userId).build());
expressionValues.put(":minAge", AttributeValue.builder().n(String.valueOf(minAge)).build());
QueryRequest request = QueryRequest.builder()
.tableName(tableName)
.keyConditionExpression("userId = :userId")
.filterExpression("age >= :minAge")
.expressionAttributeValues(expressionValues)
.build();
QueryResponse response = dynamoDbClient.query(request);
System.out.println("Found " + response.count() + " users with age >= " + minAge);
response.items().forEach(this::printUser);
}
// Scan with filter
public void scanUsersWithFilter(int minAge) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":minAge", AttributeValue.builder().n(String.valueOf(minAge)).build());
ScanRequest request = ScanRequest.builder()
.tableName(tableName)
.filterExpression("age >= :minAge")
.expressionAttributeValues(expressionValues)
.limit(100) // Limit results
.build();
ScanResponse response = dynamoDbClient.scan(request);
System.out.println("Scanned " + response.count() + " users with age >= " + minAge);
response.items().forEach(this::printUser);
// Handle pagination
while (response.lastEvaluatedKey() != null && !response.lastEvaluatedKey().isEmpty()) {
request = request.toBuilder()
.exclusiveStartKey(response.lastEvaluatedKey())
.build();
response = dynamoDbClient.scan(request);
response.items().forEach(this::printUser);
}
}
// Enhanced client query
public void enhancedQueryUsers(DynamoDbEnhancedClient enhancedClient, String userId) {
DynamoDbTable<User> userTable = enhancedClient.table("Users", TableSchema.fromBean(User.class));
QueryConditional queryConditional = QueryConditional.keyEqualTo(Key.builder()
.partitionValue(userId)
.build());
List<User> users = userTable.query(r -> r.queryConditional(queryConditional))
.items()
.stream()
.collect(Collectors.toList());
System.out.println("Enhanced query found: " + users.size() + " users");
users.forEach(user -> System.out.println(user.getName() + " - " + user.getEmail()));
}
private void printUser(Map<String, AttributeValue> user) {
System.out.println("User: " + user.get("userId").s() + 
" - " + user.get("name").s() + 
" - Age: " + user.get("age").n());
}
}

Advanced Operations

Example 5: Transactions and Conditional Writes

public class AdvancedOperations {
private final DynamoDbClient dynamoDbClient;
public AdvancedOperations(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
// Conditional write - only update if age is less than current value
public void updateUserIfYounger(String userId, String email, int newAge) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userId).build());
key.put("email", AttributeValue.builder().s(email).build());
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":newAge", AttributeValue.builder().n(String.valueOf(newAge)).build());
expressionValues.put(":currentAge", AttributeValue.builder().n(String.valueOf(newAge)).build());
UpdateItemRequest request = UpdateItemRequest.builder()
.tableName("Users")
.key(key)
.updateExpression("SET age = :newAge, updatedAt = :now")
.conditionExpression("age <= :currentAge") // Only update if current age is less or equal
.expressionAttributeValues(expressionValues)
.build();
try {
dynamoDbClient.updateItem(request);
System.out.println("User age updated to: " + newAge);
} catch (ConditionalCheckFailedException e) {
System.out.println("Condition check failed: User is already younger or same age");
}
}
// Transaction write - multiple operations in single transaction
public void transferBalance(String fromUserId, String toUserId, double amount) {
// This would require a Transactions table with balance attribute
TransactWriteItemsRequest request = TransactWriteItemsRequest.builder()
.transactItems(
TransactWriteItem.builder()
.update(Update.builder()
.tableName("Accounts")
.key(Map.of(
"accountId", AttributeValue.builder().s(fromUserId).build()
))
.updateExpression("SET balance = balance - :amount")
.conditionExpression("balance >= :amount")
.expressionAttributeValues(Map.of(
":amount", AttributeValue.builder().n(String.valueOf(amount)).build()
))
.build())
.build(),
TransactWriteItem.builder()
.update(Update.builder()
.tableName("Accounts")
.key(Map.of(
"accountId", AttributeValue.builder().s(toUserId).build()
))
.updateExpression("SET balance = balance + :amount")
.expressionAttributeValues(Map.of(
":amount", AttributeValue.builder().n(String.valueOf(amount)).build()
))
.build())
.build()
)
.build();
try {
dynamoDbClient.transactWriteItems(request);
System.out.println("Transfer successful: " + amount + " from " + fromUserId + " to " + toUserId);
} catch (TransactionCanceledException e) {
System.out.println("Transfer failed: " + e.cancellationReasons());
}
}
// Batch write items
public void batchWriteUsers(List<User> users) {
List<WriteRequest> writeRequests = users.stream()
.map(user -> {
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(user.getUserId()).build());
item.put("email", AttributeValue.builder().s(user.getEmail()).build());
item.put("name", AttributeValue.builder().s(user.getName()).build());
item.put("age", AttributeValue.builder().n(String.valueOf(user.getAge())).build());
item.put("createdAt", AttributeValue.builder().s(java.time.Instant.now().toString()).build());
return WriteRequest.builder()
.putRequest(PutRequest.builder().item(item).build())
.build();
})
.collect(Collectors.toList());
Map<String, List<WriteRequest>> requestItems = new HashMap<>();
requestItems.put("Users", writeRequests);
BatchWriteItemRequest request = BatchWriteItemRequest.builder()
.requestItems(requestItems)
.build();
BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(request);
System.out.println("Batch write completed. Unprocessed items: " + response.unprocessedItems().size());
}
// Batch get items
public void batchGetUsers(List<String> userIds, List<String> emails) {
if (userIds.size() != emails.size()) {
throw new IllegalArgumentException("User IDs and emails lists must have same size");
}
List<Map<String, AttributeValue>> keys = new ArrayList<>();
for (int i = 0; i < userIds.size(); i++) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userIds.get(i)).build());
key.put("email", AttributeValue.builder().s(emails.get(i)).build());
keys.add(key);
}
BatchGetItemRequest request = BatchGetItemRequest.builder()
.requestItems(Map.of(
"Users", KeysAndAttributes.builder().keys(keys).build()
))
.build();
BatchGetItemResponse response = dynamoDbClient.batchGetItem(request);
List<Map<String, AttributeValue>> users = response.responses().get("Users");
System.out.println("Batch get retrieved: " + users.size() + " users");
users.forEach(this::printUser);
}
private void printUser(Map<String, AttributeValue> user) {
System.out.println("User: " + user.get("userId").s() + " - " + user.get("name").s());
}
}

Async Operations

Example 6: Asynchronous Operations

public class AsyncOperations {
private final DynamoDbAsyncClient asyncClient;
public AsyncOperations(DynamoDbAsyncClient asyncClient) {
this.asyncClient = asyncClient;
}
public CompletableFuture<Void> asyncPutUser(String userId, String email, String name, int age) {
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(userId).build());
item.put("email", AttributeValue.builder().s(email).build());
item.put("name", AttributeValue.builder().s(name).build());
item.put("age", AttributeValue.builder().n(String.valueOf(age)).build());
item.put("createdAt", AttributeValue.builder().s(java.time.Instant.now().toString()).build());
PutItemRequest request = PutItemRequest.builder()
.tableName("Users")
.item(item)
.build();
return asyncClient.putItem(request)
.thenAccept(response -> {
System.out.println("Async user created: " + userId);
})
.exceptionally(throwable -> {
System.err.println("Error creating user: " + throwable.getMessage());
return null;
});
}
public CompletableFuture<Map<String, AttributeValue>> asyncGetUser(String userId, String email) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("userId", AttributeValue.builder().s(userId).build());
key.put("email", AttributeValue.builder().s(email).build());
GetItemRequest request = GetItemRequest.builder()
.tableName("Users")
.key(key)
.build();
return asyncClient.getItem(request)
.thenApply(GetItemResponse::item)
.exceptionally(throwable -> {
System.err.println("Error getting user: " + throwable.getMessage());
return Collections.emptyMap();
});
}
// Multiple async operations
public CompletableFuture<Void> performMultipleAsyncOperations() {
List<CompletableFuture<Void>> futures = new ArrayList<>();
// Create multiple users async
for (int i = 1; i <= 5; i++) {
String userId = "user-" + i;
String email = "user" + i + "@example.com";
String name = "User " + i;
int age = 20 + i;
CompletableFuture<Void> future = asyncPutUser(userId, email, name, age);
futures.add(future);
}
// Wait for all operations to complete
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> System.out.println("All async operations completed"));
}
// Async query
public CompletableFuture<Void> asyncQueryUsers(String userId) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":userId", AttributeValue.builder().s(userId).build());
QueryRequest request = QueryRequest.builder()
.tableName("Users")
.keyConditionExpression("userId = :userId")
.expressionAttributeValues(expressionValues)
.build();
return asyncClient.query(request)
.thenAccept(response -> {
System.out.println("Async query found " + response.count() + " users");
response.items().forEach(item -> 
System.out.println("User: " + item.get("name").s()));
})
.exceptionally(throwable -> {
System.err.println("Error querying users: " + throwable.getMessage());
return null;
});
}
}

Secondary Indexes

Example 7: Global and Local Secondary Indexes

public class SecondaryIndexOperations {
private final DynamoDbClient dynamoDbClient;
public SecondaryIndexOperations(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
// Create table with Global Secondary Index (GSI)
public void createTableWithGSI() {
CreateTableRequest request = CreateTableRequest.builder()
.tableName("Products")
.keySchema(
KeySchemaElement.builder()
.attributeName("productId")
.keyType(KeyType.HASH)
.build()
)
.attributeDefinitions(
AttributeDefinition.builder()
.attributeName("productId")
.attributeType(ScalarAttributeType.S)
.build(),
AttributeDefinition.builder()
.attributeName("category")
.attributeType(ScalarAttributeType.S)
.build(),
AttributeDefinition.builder()
.attributeName("price")
.attributeType(ScalarAttributeType.N)
.build()
)
.globalSecondaryIndexes(
GlobalSecondaryIndex.builder()
.indexName("CategoryPriceIndex")
.keySchema(
KeySchemaElement.builder()
.attributeName("category")
.keyType(KeyType.HASH)
.build(),
KeySchemaElement.builder()
.attributeName("price")
.keyType(KeyType.RANGE)
.build()
)
.projection(Projection.builder()
.projectionType(ProjectionType.ALL)
.build())
.provisionedThroughput(ProvisionedThroughput.builder()
.readCapacityUnits(5L)
.writeCapacityUnits(5L)
.build())
.build()
)
.billingMode(BillingMode.PAY_PER_REQUEST)
.build();
dynamoDbClient.createTable(request);
System.out.println("Table with GSI created");
}
// Query using GSI
public void queryByCategoryAndPrice(String category, double maxPrice) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":category", AttributeValue.builder().s(category).build());
expressionValues.put(":maxPrice", AttributeValue.builder().n(String.valueOf(maxPrice)).build());
QueryRequest request = QueryRequest.builder()
.tableName("Products")
.indexName("CategoryPriceIndex")
.keyConditionExpression("category = :category AND price <= :maxPrice")
.expressionAttributeValues(expressionValues)
.build();
QueryResponse response = dynamoDbClient.query(request);
System.out.println("Found " + response.count() + " products in category: " + category);
response.items().forEach(item -> {
System.out.println("Product: " + item.get("productId").s() + 
" - " + item.get("name").s() + 
" - $" + item.get("price").n());
});
}
// Scan using GSI
public void scanByCategory(String category) {
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":category", AttributeValue.builder().s(category).build());
ScanRequest request = ScanRequest.builder()
.tableName("Products")
.indexName("CategoryPriceIndex")
.filterExpression("category = :category")
.expressionAttributeValues(expressionValues)
.build();
ScanResponse response = dynamoDbClient.scan(request);
System.out.println("Scanned " + response.count() + " products in category: " + category);
}
}

Error Handling and Best Practices

Example 8: Comprehensive Error Handling

public class ErrorHandlingExamples {
private final DynamoDbClient dynamoDbClient;
public ErrorHandlingExamples(DynamoDbClient dynamoDbClient) {
this.dynamoDbClient = dynamoDbClient;
}
public void putUserWithErrorHandling(User user) {
try {
// Convert user to attribute map
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(user.getUserId()).build());
item.put("email", AttributeValue.builder().s(user.getEmail()).build());
item.put("name", AttributeValue.builder().s(user.getName()).build());
item.put("age", AttributeValue.builder().n(String.valueOf(user.getAge())).build());
PutItemRequest request = PutItemRequest.builder()
.tableName("Users")
.item(item)
.conditionExpression("attribute_not_exists(userId)") // Only put if doesn't exist
.build();
dynamoDbClient.putItem(request);
System.out.println("User created successfully: " + user.getUserId());
} catch (ConditionalCheckFailedException e) {
System.out.println("User already exists: " + user.getUserId());
} catch (ResourceNotFoundException e) {
System.err.println("Table not found: " + e.getMessage());
} catch (ProvisionedThroughputExceededException e) {
System.err.println("Throughput exceeded, implementing retry...");
// Implement retry logic with exponential backoff
retryPutUser(user);
} catch (DynamoDbException e) {
System.err.println("DynamoDB error: " + e.getMessage());
// Log error for debugging
e.printStackTrace();
}
}
private void retryPutUser(User user) {
// Simple retry logic with exponential backoff
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
Thread.sleep((long) Math.pow(2, retryCount) * 1000); // Exponential backoff
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(user.getUserId()).build());
item.put("email", AttributeValue.builder().s(user.getEmail()).build());
// ... other attributes
PutItemRequest request = PutItemRequest.builder()
.tableName("Users")
.item(item)
.build();
dynamoDbClient.putItem(request);
System.out.println("User created after retry: " + user.getUserId());
return;
} catch (Exception e) {
retryCount++;
System.err.println("Retry " + retryCount + " failed: " + e.getMessage());
}
}
System.err.println("Failed to create user after " + maxRetries + " retries: " + user.getUserId());
}
public void batchOperationWithErrorHandling(List<User> users) {
List<WriteRequest> writeRequests = users.stream()
.map(user -> {
Map<String, AttributeValue> item = new HashMap<>();
item.put("userId", AttributeValue.builder().s(user.getUserId()).build());
item.put("email", AttributeValue.builder().s(user.getEmail()).build());
item.put("name", AttributeValue.builder().s(user.getName()).build());
item.put("age", AttributeValue.builder().n(String.valueOf(user.getAge())).build());
return WriteRequest.builder()
.putRequest(PutRequest.builder().item(item).build())
.build();
})
.collect(Collectors.toList());
Map<String, List<WriteRequest>> requestItems = Map.of("Users", writeRequests);
try {
BatchWriteItemRequest request = BatchWriteItemRequest.builder()
.requestItems(requestItems)
.build();
BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(request);
// Check for unprocessed items
if (!response.unprocessedItems().isEmpty()) {
System.out.println("Some items were not processed, retrying...");
retryBatchWrite(response.unprocessedItems());
}
} catch (DynamoDbException e) {
System.err.println("Batch write failed: " + e.getMessage());
}
}
private void retryBatchWrite(Map<String, List<WriteRequest>> unprocessedItems) {
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries && !unprocessedItems.isEmpty()) {
try {
Thread.sleep((long) Math.pow(2, retryCount) * 1000);
BatchWriteItemRequest retryRequest = BatchWriteItemRequest.builder()
.requestItems(unprocessedItems)
.build();
BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(retryRequest);
unprocessedItems = response.unprocessedItems();
retryCount++;
} catch (Exception e) {
System.err.println("Retry " + retryCount + " failed: " + e.getMessage());
retryCount++;
}
}
if (!unprocessedItems.isEmpty()) {
System.err.println("Some items remain unprocessed after retries: " + unprocessedItems.size());
}
}
}

Best Practices Summary

  1. Use appropriate capacity modes (Provisioned vs. On-Demand)
  2. Design efficient key schemas for your access patterns
  3. Use secondary indexes for alternative query patterns
  4. Implement retry logic with exponential backoff
  5. Use batch operations for better throughput
  6. Monitor performance with CloudWatch metrics
  7. Use conditional writes for data consistency
  8. Implement proper error handling for all operations
  9. Use the Enhanced Client for object persistence when possible
  10. Consider async operations for high-throughput scenarios

This comprehensive guide covers the essential aspects of working with DynamoDB in Java, from basic CRUD operations to advanced features like transactions, secondary indexes, and error handling.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper