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.