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
- Use appropriate capacity modes (Provisioned vs. On-Demand)
- Design efficient key schemas for your access patterns
- Use secondary indexes for alternative query patterns
- Implement retry logic with exponential backoff
- Use batch operations for better throughput
- Monitor performance with CloudWatch metrics
- Use conditional writes for data consistency
- Implement proper error handling for all operations
- Use the Enhanced Client for object persistence when possible
- 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.