How to migrate from DynamoDb Java SDK v1 to v2

Serhat Can
7 min readNov 13, 2020

I use DynamoDb in my side projects unless there is a good reason otherwise. I really like DynamoDb because most of the time it feels like I’m interacting with a well-designed API, rather than a database.

My preferred language is Java because I’m originally a Java developer and it helps me build things faster. AWS SDK for Java v1 has been around for a long time. It is mature but the new v2 is what is next and offers some advantages such as improved consistency, ease of use, and strongly enforced immutability.

The migration from v1 to v2 is not difficult once you figure out some key changes. I’ll do my best to share important ones with code examples in this blog post. This blog post is not an intro to DynamoDb blog post. If you are new to Dynamo, you may want to take a look at the dynamodbguide.com first. This blog post should have everything that I wish I had before the migration :)

For the sake of clarity, I assume you already know how to set up AWS credentials (here is how).

First: Import dependencies

This is well documented here. I use Apache Maven. If you use BOM (a good practice considering many different libraries within AWS SDK), update these two lines:

<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>

As this:

<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>

Then you need to update specific dependencies for different AWS services and SDK helpers. For example, the new Dynamo dependency looks like this:

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
</dependency

One thing to note is that some important artifactIds are not the same. The new SDK v2 has sdk-core, auth, regions, aws-query-protocol artifacts that are useful in many cases. When you are done, run maven clean complie and you should be ready.

The difference between dynamo and dynamo-enhanced

Before moving into actual coding, you will need to know the difference between dynamo and dynamo-enhanced dependencies. Yes, there are two different artifacts for Dynamo within Java SDK v2.

I decided to use both because dynamo-enhanced is essentially a wrapper for dynamodb. In fact, you pass your dynamodb client object to dynamodb-enhanced client object when creating it.

Regular dynamodb dependency has everything you need but dynamodb-enhanced offers an easier syntax and annotation capabilities that will help you create your tables and queries by just looking at data classes — again like the v1.

My suggestion is to import both and use dynamo-enhanced whenever possible. There may be nuances I’m not aware of but the available functionality shows that enhanced is pretty powerful and easy to use.

Creating client objects

I use Spring boot and because Spring is widely used, I’ll use some Spring specific syntax. The first thing you will notice when you start coding with AWS SDK v2 for Java is you don’t create new objects using the new keyword. You will use lots of builders.

Here is how you can create DynamoDbClient and DynamoDbEnhancedClient objects. You can access them using @Autowired keyword in your Spring app once you register them as beans. Here is how to create the client for your local DynamoDb instance:

@Bean
DynamoDbClient amazonDynamoDBClient() {
return getDynamoDbClient();
}

@Bean
DynamoDbEnhancedClient amazonDynamoDBEnhancedClient() {
return DynamoDbEnhancedClient.builder().dynamoDbClient(getDynamoDbClient()).build();
}

private DynamoDbClient getDynamoDbClient() {
ClientOverrideConfiguration.Builder overrideConfig =
ClientOverrideConfiguration.builder();

return DynamoDbClient.builder()
.overrideConfiguration(overrideConfig.build())
.endpointOverride(URI.create("http://localhost:8000"))
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
}

Creating tables — the hard way

There are different ways you can create Tables with Java SDK v2. First, you can use DynamoDBClient and create a CreateTableRequest and then call dynamoClient.createTable(createTableRequest). This page has some details. If you use this approach and also use a GSI (Global Secondary Index), here is an example (the official page doesn’t have one).

CreateTableRequest req = CreateTableRequest.builder()
.tableName(User.TABLE_NAME)
.attributeDefinitions(
AttributeDefinition.builder()
.attributeName("id")
.attributeType(ScalarAttributeType.S)
.build(),
AttributeDefinition.builder()
.attributeName("email")
.attributeType(ScalarAttributeType.S)
.build()
)
.keySchema(
KeySchemaElement.builder()
.attributeName("id")
.keyType(KeyType.HASH)
.build()
)
.globalSecondaryIndexes(
GlobalSecondaryIndex.builder()
.indexName(User.GSI_EMAIL)
.keySchema(
KeySchemaElement.builder()
.attributeName("email")
.keyType(KeyType.HASH)
.build())
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build())
.projection(Projection.builder().projectionType(ProjectionType.ALL).build())
.build())
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build())
.billingMode(BillingMode.PAY_PER_REQUEST)
.build();

Now, we need to talk about the new Entity annotations before I show you how to create tables easily without all these manual definitions.

Entity annotations

Entity annotations are all different in v2.

The new table annotation in v2 doesn’t have a way of specifying the table name like the previous one. Replace @DynamoDBTable(tableName = user) with @DynamoDbBean. Then, write your Table name as a static String in the entity itself to avoid typos. I use User.TABLE_NAME it when I need to refer to the table’s name. You can also create a separate class to hold all table name and use it as TableNames.USER.

Important: While creating entity objects, don’t create setters using the Builder pattern. I don’t exactly know why and if they will fix it but “return this;” doesn’t work with SDK v2 automated object mapping. Use regular getter and setter methods — not builder patterned setters.

When you create tables manually in v2, you refer to the Partition key as the Hash key and the Sort key as the Range Key. Things are different in the enhanced client and annotations. I think in SDK v1, the way we refer to index annotations was easier to understand but the new is also easy once you get used to it.

For Partition Key: replace @DynamoDBHaskKey with @DynamoDbPartitationKey

For Sort Key: Replace @DynamoDBRangeKey with @DynamoDbSortKey

For an Index Partition Key: Replace @DynamoDBIndexHashKey(globalSecondaryIndexName = GSI_NAME with @DynamoDbSecondaryPartitationKey(indexNames = {GSI_NAME}

For an Index Sort Key: Replace @DynamoDBIndexRangeKey(globalSecondaryIndexName = GSI_NAME with @DynamoDbSecondarySortKey(indexNames = {GSI_NAME}

Now, let me show you an example. For example, a User object with:

  • a customerId field as Hash Key (Partition Key)
  • an id field as Range Key (Sort Key) for our Partition Key and GSI
  • an email field as Partition key for the GSI
@DynamoDbPartitionKey
public String getAccountId() {
return accountId;
}

@DynamoDbSortKey
@DynamoDbSecondarySortKey(indexNames = {GSI_EMAIL})
public String getId() {
return id;
}

@DynamoDbSecondaryPartitionKey(indexNames = {GSI_EMAIL})
public String getEmail() {
return email;
}

Creating tables — the easy way: DynamoDbEnhancedClient

You learned how to annotate entities. Let’s see how to create our tables using the DynamoDbEnhancedClient. This is an easier and safer way of creating tables. AWS doesn’t have this in the official doc but there is an example here in the official dynamodb-enhanced GitHub page.

Like many operations in the enhanced client, you first need a Table instance. Then using that table instance, you can create the table and do all the other crud operations. If you don’t have an index for your table, you just take a reference and create the table like this:

dynamo.table(User.TABLE_NAME, TableSchema.fromClass(User.class)).createTable();

If you have an index, you must pass a CreateTableRequest object with the necessary configuration. Here is an example:

CreateTableEnhancedRequest request = CreateTableEnhancedRequest.builder()
.globalSecondaryIndices(
EnhancedGlobalSecondaryIndex.builder()
.indexName(User.GSI_EMAIL)
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build())
.projection(Projection.builder().projectionType(ProjectionType.ALL).build())
.build())
.build();
dynamo.table(User.TABLE_NAME, TableSchema.fromClass(User.class)).createTable(request);

Now you know how to annotate entities and create tables automatically. After the table deletion, I’ll share CRUD operation examples. Hang on.

Deleting tables

This is easy. You just need to pass on the table name to DynamoDbClient. The enhanced client doesn’t have this operation. Use the regular client, not the enhanced one.

One thing you will notice is that you can’t just pass String fields anymore. As you will see in the next examples, you will need to build objects — don’t worry that’s also very easy.

dynamoClient.deleteTable(
DeleteTableRequest.builder().tableName(tableName).build()
);

Working with Table and Index objects

The enhanced client requires you to create Table objects to execute CRUD operations on tables and Index objects to execute CRUD operations on indexes. In the v1 SDK, we could execute operations using the Dynamo mapper (created using the client) like this:

mapper.load(User.class, id); // GET examplemapper.save(user); // CREATE/Save example

In the new v2 enhanced client, we need to first create a reference to a table or index like this (example from a Spring repository):

private final DynamoDbTable<User> userTable;

public UserRepository(@Autowired DynamoDbEnhancedClient dynamo) {
this.userTable = dynamo.table(User.TABLE_NAME, TableSchema.fromBean(User.class));
}

From that userTable object, we can get a reference for an index:

DynamoDbIndex<User> emailIndex = userTable.index(User.GSI_EMAIL);

Let’s how to do CRUD operations using these objects.

Creating (Put) entities

Assuming you have the DynamoDbTable<User> userTableobject like I showed you just now, you can create an entity like this:

userTable.putItem(user);

If you need specific configuration, you build a PutItemEnhancedRequest using the builder like this:

PutItemEnhancedRequest.builder().build() // fill in the blanks

Notice that we use Put instead of Save in the v2 enhanced client.

Get entity

Notice you need to build the Key object using the builder. We don’t have load method anymore. userTable already knows the object, so it needs the key to get the item.

userTable.getItem(Key.builder().partitionValue(accountId).sortValue(userId).build());

Query entity

This is how to query an entity from a table:

userTable.query(q).items().stream().collect(Collectors.toList());

And this is how to get an entity from an index:

DynamoDbIndex<User> emailIndex = userTable.index(User.GSI_EMAIL);
QueryConditional q = QueryConditional.keyEqualTo(Key.builder().partitionValue(email).sortValue(accountId).build());
Iterator<Page<User>> result = emailIndex.query(q).iterator();
List<User> users = new ArrayList<>();
while (result.hasNext()) {
Page<User> userPage = result.next();
users.addAll(userPage.items());
}

Update entity

Updating an entity is simple. Just pass the whole object or build an UpdateItemEnhancedRequest object if you need partial updates or something.

userTable.updateItem(user);

Delete entity

You can delete entities by passing objects like a User object, by Id or using DeleteItemEnhancedRequest.

userTable.deleteItem(Key.builder().partitionValue(accountId).sortValue(userId).build())

To sum up

In this blog post, I tried to show you how to migrate from Java SDK v1 to v2 for DynamoDB and the most required changes with code examples. It is not easy but it is also not that hard. I don’t know if migrating pays off but I know that there are some serious performance improvements in the new client.

I tried to write down all the things I wish I know before I did the migration. But I also know that I’m not an expert user of this client yet, so be careful about the details and let me know if there is any mistake I made.

I hope this blog post helps someone. Cheers folks!

Stay connected with me on Twitter and LinkedIn.

Useful links

--

--