

# Changes in working with DynamoDB from version 1 to version 2 of the AWS SDK for Java
<a name="migration-ddb-mapper"></a>



**Topics**
+ [DynamoDB mapping API differences between version 1 and version 2 of the AWS SDK for Java](ddb-mapping.md)
+ [Document API differences between version 1 and version 2 of the AWS SDK for Java](dynamodb-mapping-document-api.md)
+ [Encryption library migration](ddb-encryption-lib-migrate.md)

# DynamoDB mapping API differences between version 1 and version 2 of the AWS SDK for Java
<a name="ddb-mapping"></a>

The DynamoDB mapping APIs changed significantly between version 1 and version 2 of the AWS SDK for Java. In version 1, you use the `DynamoDBMapper` to work with Java POJOs. In version 2, you use the `DynamoDbEnhancedClient` with updated method names, enhanced schema definition options, and improved type safety.

Key differences include:
+ New method names (such as `getItem` instead of `load`)
+ Explicit table schema creation
+ Built-in support for both synchronous and asynchronous operations
+ Changes in how empty strings and configuration are handled

This section covers the mapping API changes, annotation differences, configuration updates, and migration guidance to help you transition from the v1 `DynamoDBMapper` to the v2 `DynamoDbEnhancedClient`.

**Contents**
+ [High-level changes in mapping libraries from version 1 to version 2 of the SDK for Java](dynamodb-mapping-high-level.md)
  + [Import dependency differences](dynamodb-mapping-high-level.md#dynamodb-mapping-deps)
+ [Changes in the DynamoDB mapping APIs between version 1 and version 2 of the SDK for Java](dynamodb-mapping-api-changes.md)
  + [Create a client](dynamodb-mapping-api-changes.md#dynamodb-mapping-api-changes-client)
  + [Establish mapping to DynamoDB table/index](dynamodb-mapping-api-changes.md#dynamodb-mapping-api-changes-mapping)
  + [Table operations](dynamodb-mapping-api-changes.md#dynamodb-mapping-api-changes-tobleops)
  + [Map classes and properties](dynamodb-mapping-api-changes.md#dynamodb-mapping-schemas)
    + [Bean annotations](dynamodb-mapping-api-changes.md#dynamodb-mapping-schemas-annos)
    + [V2 additional annotations](dynamodb-mapping-api-changes.md#dynamodb-mapping-schemas-annos-v2-addnl)
  + [Configuration](dynamodb-mapping-api-changes.md#dynamodb-mapping-configuration)
    + [Per-operation configuration](dynamodb-mapping-api-changes.md#dynamodb-mapping-configuration-per-op)
  + [Conditionals](dynamodb-mapping-api-changes.md#dynamodb-mapping-conditionals)
  + [Type conversion](dynamodb-mapping-api-changes.md#dynamodb-mapping-type-conv)
    + [Default converters](dynamodb-mapping-api-changes.md#dynamodb-mapping-type-conv-defaults)
    + [Set a custom converter for an attribute](dynamodb-mapping-api-changes.md#dynamodb-mapping-type-conv-anno)
    + [Add a type converter factory or provider](dynamodb-mapping-api-changes.md#dynamodb-mapping-type-conv-factory)
+ [String handling differences between version 1 and version 2 of the SDK for Java](dynamodb-migration-string-handling.md)
+ [Optimistic locking differences between version 1 and version 2 of the SDK for Java](dynamodb-migrate-optimstic-locking.md)
+ [Fluent setters differences between version 1 and version 2 of the SDK for Java](dynamodb-migrate-fluent-setters.md)

# High-level changes in mapping libraries from version 1 to version 2 of the SDK for Java
<a name="dynamodb-mapping-high-level"></a>

The names of the mapping client in each library differ in V1 and V2:
+ V1 - DynamoDBMapper
+ V2 - DynamoDB Enhanced Client

You interact with the two libraries in much the same way: you instantiate a mapper/client and then supply a Java POJO to APIs that read and write these items to DynamoDB tables. Both libraries also offer annotations for the class of the POJO to direct how the client handles the POJO. 

Notable differences when you move to V2 include:
+ V2 and V1 use different method names for the low-level DynamoDB operations. For example:    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-mapping-high-level.html)
+ V2 offers multiple ways to define table schemas and map POJOs to tables. You can choose from the use of annotations or a schema generated from code using a builder. V2 also offers mutable and immutable versions of schemas.
+ With V2, you specifically create the table schema as one of the first steps, whereas in V1, the table schema is inferred from the annotated class as needed.
+ V2 includes the [Document API client ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html) in the enhanced client API, whereas V1 uses a [separate API](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html).
+ All APIs are available in synchronous and asynchronous versions in V2.

See the [DynamoDB mapping section](dynamodb-enhanced-client.md) in this guide for more detailed information on the V2 enhanced client.

## Import dependency differences
<a name="dynamodb-mapping-deps"></a>


| V1 | V2 | 
| --- | --- | 
|  <pre><dependencyManagement><br />  <dependencies><br />    <dependency><br />      <groupId>com.amazonaws</groupId><br />      <artifactId>aws-java-sdk-bom</artifactId><br />      <version>1.X.X</version><br />      <type>pom</type><br />      <scope>import</scope><br />    </dependency><br />  </dependencies><br /></dependencyManagement> <br /><br /><dependencies><br />  <dependency><br />    <groupId>com.amazonaws</groupId><br />    <artifactId>aws-java-sdk-dynamodb</artifactId><br />  </dependency><br /></dependencies></pre>  |  <pre><dependencyManagement><br />  <dependencies><br />    <dependency><br />      <groupId>software.amazon.awssdk</groupId><br />      <artifactId>bom</artifactId><br />      <version>2.X.X*</version><br />      <type>pom</type><br />      <scope>import</scope><br />    </dependency><br />  </dependencies><br /></dependencyManagement> <br /><br /><dependencies><br />  <dependency><br />    <groupId>software.amazon.awssdk</groupId><br />    <artifactId>dynamodb-enhanced</artifactId><br />  </dependency><br /></dependencies></pre>  | 

\$1 [Latest version](https://central.sonatype.com/artifact/software.amazon.awssdk/bom).

In V1, a single dependency includes both the low-level DynamoDB API and the mapping/document API, whereas in V2, you use the `dynamodb-enhanced` artifact dependency to access the mapping/document API. The `dynamodb-enhanced` module contains a transitive dependency on the low-level `dynamodb` module. 

# Changes in the DynamoDB mapping APIs between version 1 and version 2 of the SDK for Java
<a name="dynamodb-mapping-api-changes"></a>

## Create a client
<a name="dynamodb-mapping-api-changes-client"></a>


****  

| Use case | V1 | V2 | 
| --- | --- | --- | 
|   Normal instantiation  |  <pre>AmazonDynamoDB standardClient = AmazonDynamoDBClientBuilder.standard()<br />    .withCredentials(credentialsProvider)<br />    .withRegion(Regions.US_EAST_1)<br />    .build();<br />DynamoDBMapper mapper = new DynamoDBMapper(standardClient);</pre>  |  <pre>DynamoDbClient standardClient = DynamoDbClient.builder()<br />    .credentialsProvider(ProfileCredentialsProvider.create())<br />    .region(Region.US_EAST_1)<br />    .build();<br />DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()<br />    .dynamoDbClient(standardClient)<br />    .build();</pre>  | 
|   Minimal instantiation  |  <pre>AmazonDynamoDB standardClient = AmazonDynamoDBClientBuilder.standard();<br />DynamoDBMapper mapper = new DynamoDBMapper(standardClient);</pre>  |  <pre>DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();</pre>  | 
|   With attribute transformer\$1  |  <pre>DynamoDBMapper mapper = new DynamoDBMapper(standardClient, <br />                        attributeTransformerInstance);</pre>  |  <pre>DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()<br />    .dynamoDbClient(standardClient)<br />    .extensions(extensionAInstance, extensionBInstance)<br />    .build();</pre>  | 

\$1Extensions in V2 correspond roughly to attribute transformers in V1. The [Use extensions to customize DynamoDB Enhanced Client operations](ddb-en-client-extensions.md) section contains more information on extensions in V2. 

## Establish mapping to DynamoDB table/index
<a name="dynamodb-mapping-api-changes-mapping"></a>

In V1, you specify a DynamoDB table name through a bean annotation. In V2, a factory method, `table()`, produces an instance of `DynamoDbTable` that represents the remote DynamoDB table. The first parameter of the `table()` method is the DynamoDB table name.


****  

| Use case | V1 | V2 | 
| --- | --- | --- | 
|   Map the Java POJO class to the DynamoDB table  |  <pre>@DynamoDBTable(tableName ="Customer")<br />public class Customer {<br />  ...<br />}</pre>  |  <pre>DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer",<br />    TableSchema.fromBean(Customer.class));</pre>  | 
|   Map to a DynamoDB secondary index  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-mapping-api-changes.html) The section in the DynamoDB Developer Guide that discusses[ the V1 `query` method](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.Methods.html#DynamoDBMapper.Methods.query) shows a complete example.  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-mapping-api-changes.html) The [Use secondary indices](ddb-en-client-use-secindex.md) section in this guide provides more information.  | 

## Table operations
<a name="dynamodb-mapping-api-changes-tobleops"></a>

This section describes operation APIs that differ between V1 and V2 for most standard use cases. 

In V2, all operations that involve a single table are called on the `DynamoDbTable` instance, not on the enhanced client. The enhanced client contains methods that can target multiple tables. 

In the table named *Table operations* below, a POJO instance is referred to as `item` or as a specific type such as `customer1`. For the V2 examples the instances named, `table` is the result of previously calling `enhancedClient.table()` that returns a reference to the `DynamoDbTable` instance.

Note that most V2 operations can be called with a fluent consumer pattern even when not shown. For example,

```
Customer customer = table.getItem(r → r.key(key));
  or
Customer customer = table.getItem(r → r.key(k -> k.partitionValue("id").sortValue("email")))
```

For V1 operations, *Table operations* (below) contains some of the commonly used forms and not all overloaded forms. For example, the `load()` method has the following overloads:

```
mapper.load(Customer.class, hashKey)
mapper.load(Customer.class, hashKey, rangeKey)
mapper.load(Customer.class, hashKey, config)
mapper.load(Customer.class, hashKey, rangeKey, config)
mapper.load(item)
mapper.load(item, config)
```

*Table operations* (below) shows the commonly used forms:

```
mapper.load(item)
mapper.load(item, config)
```


**Table operations**  

| Use case | V1 | V2 | 
| --- | --- | --- | 
|  Write a Java POJO to a DynamoDB table **DynamoDB operation:** `PutItem`, `UpdateItem`  |  <pre>mapper.save(item)<br />mapper.save(item, config)<br />mapper.save(item, saveExpression, config)</pre> In V1, `DynamoDBMapperConfig.SaveBehavior` and annotations determines which low-level DynamoDB method will be called. In general, `UpdateItem` is called except when using `SaveBehavior.CLOBBER` and `SaveBehavior.PUT`. Auto-generated keys are a special use case, and occasionally both `PutItem` and `UpdateItem` are used.  |  <pre>table.putItem(putItemRequest)<br />table.putItem(item)<br />table.putItemWithResponse(item) //Returns metadata.<br /><br />updateItem(updateItemRequest)<br />table.updateItem(item)<br />table.updateItemWithResponse(item) //Returns metadata.</pre>  | 
|  Read an item from a DynamoDB table to a Java POJO **DynamoDB operation:** `GetItem`  |  <pre>mapper.load(item)<br />mapper.load(item, config)</pre>  |  <pre>table.getItem(getItemRequest)<br />table.getItem(item)<br />table.getItem(key)<br />table.getItemWithResponse(key) //Returns POJO with metadata.</pre>  | 
|  Delete an item from a DynamoDB table **DynamoDB operation:** `DeleteItem`  |  <pre>mapper.delete(item, deleteExpression, config)</pre>  |  <pre>table.deleteItem(deleteItemRequest)<br />table.deleteItem(item)<br />table.deleteItem(key)</pre>  | 
|  Query a DynamoDB table or secondary index and return a paginated list **DynamoDB operation:** `Query`  |  <pre>mapper.query(Customer.class, queryExpression)<br />mapper.query(Customer.class, queryExpression, <br />                             mapperConfig)</pre>  |  <pre>table.query(queryRequest)<br />table.query(queryConditional)</pre> Use the returned `PageIterable.stream()` (lazy loading) for sync responses and `PagePublisher.subscribe()` for async responses  | 
|  Query a DynamoDB table or secondary index and return a list **DynamoDB operation:** `Query`  |  <pre>mapper.queryPage(Customer.class, queryExpression)<br />mapper.queryPage(Customer.class, queryExpression, <br />                                 mapperConfig)</pre>  |  <pre>table.query(queryRequest)<br />table.query(queryConditional)</pre> Use the returned `PageIterable.items()` (lazy loading) for sync responses and `PagePublisher.items.subscribe()` for async responses  | 
|  Scan a DynamoDB table or secondary index and return a paginated list **DynamoDB operation:** `Scan`  |  <pre>mapper.scan(Customer.class, scanExpression)<br />mapper.scan(Customer.class, scanExpression, <br />                            mapperConfig)</pre>  |  <pre>table.scan()<br />table.scan(scanRequest)</pre> Use the returned `PageIterable.stream()` (lazy loading) for sync responses and `PagePublisher.subscribe()` for async responses  | 
|  Scan a DynamoDB table or secondary index and return a list **DynamoDB operation:** `Scan`  |  <pre>mapper.scanPage(Customer.class, scanExpression)<br />mapper.scanPage(Customer.class, scanExpression, <br />                                mapperConfig)</pre>  |  <pre>table.scan()<br />table.scan(scanRequest)</pre> Use the returned `PageIterable.items()` (lazy loading) for sync responses and `PagePublisher.items.subscribe()` for async responses  | 
|  Read multiple items from multiple tables in a batch **DynamoDB operation:** `BatchGetItem`  |  <pre>mapper.batchLoad(Arrays.asList(customer1, <br />                               customer2, <br />                               book1))<br />mapper.batchLoad(itemsToGet) <br />           // itemsToGet: Map<Class<?>, List<KeyPair>></pre>  |  <pre>enhancedClient.batchGetItem(batchGetItemRequest)<br /><br />enhancedClient.batchGetItem(r -> r.readBatches(<br />    ReadBatch.builder(Record1.class)<br />             .mappedTableResource(mappedTable1)<br />             .addGetItem(i -> i.key(k -> k.partitionValue(0)))<br />             .build(),<br />    ReadBatch.builder(Record2.class)<br />             .mappedTableResource(mappedTable2)<br />             .addGetItem(i -> i.key(k -> k.partitionValue(0)))<br />             .build()))<br /><br />// Iterate over pages with lazy loading or over all items <br />   from the same table.</pre>  | 
|  Write multiple items to multiple tables in a batch **DynamoDB operation:** `BatchWriteItem`  |  <pre>mapper.batchSave(Arrays.asList(customer1, <br />                               customer2, <br />                               book1)) </pre>  |  <pre>enhancedClient.batchWriteItem(batchWriteItemRequest)<br /><br />enhancedClient.batchWriteItem(r -> r.writeBatches(<br />    WriteBatch.builder(Record1.class)<br />             .mappedTableResource(mappedTable1)<br />             .addPutItem(item1)<br />             .build(),<br />    WriteBatch.builder(Record2.class)<br />             .mappedTableResource(mappedTable2)<br />             .addPutItem(item2)<br />             .build()))</pre>  | 
|  Delete multiple items from multiple tables in a batch **DynamoDB operation:** `BatchWriteItem`  |  <pre>mapper.batchDelete(Arrays.asList(customer1, <br />                                 customer2, <br />                                 book1)) </pre>  |  <pre>enhancedClient.batchWriteItem(r -> r.writeBatches(<br />    WriteBatch.builder(Record1.class)<br />             .mappedTableResource(mappedTable1)<br />             .addDeleteItem(item1key)<br />             .build(),<br />    WriteBatch.builder(Record2.class)<br />             .mappedTableResource(mappedTable2)<br />             .addDeleteItem(item2key)<br />             .build()))</pre>  | 
|  Write/delete multiple items in a batch **DynamoDB operation:** `BatchWriteItem`  |  <pre>mapper.batchWrite(Arrays.asList(customer1, book1), <br />                  Arrays.asList(customer2)) </pre>  |  <pre>enhancedClient.batchWriteItem(r -> r.writeBatches(<br />    WriteBatch.builder(Record1.class)<br />             .mappedTableResource(mappedTable1)<br />             .addPutItem(item1)<br />             .build(),<br />    WriteBatch.builder(Record2.class)<br />             .mappedTableResource(mappedTable2)<br />             .addDeleteItem(item2key)<br />             .build()))</pre>  | 
|  Carry out a transactional write **DynamoDB operation:** `TransactWriteItems`  |  <pre>mapper.transactionWrite(transactionWriteRequest)</pre>  |  <pre>enhancedClient.transactWriteItems(transasctWriteItemsRequest)</pre>  | 
|  Carry out a transactional read **DynamoDB operation:** `TransactGetItems`  |  <pre>mapper.transactionLoad(transactionLoadRequest)</pre>  |  <pre>enhancedClient.transactGetItems(transactGetItemsRequest) </pre>  | 
|  Get a count of matching items of a query **DynamoDB operation:** `Query` with `Select.COUNT`  |  <pre>mapper.count(Customer.class, queryExpression)</pre>  |  <pre>// Get the count from query results.<br />PageIterable<Customer> pageIterable =<br />    customerTable.query(QueryEnhancedRequest.builder()<br />        .queryConditional(queryConditional)<br />        .select(Select.COUNT)<br />        .build());<br />Iterator<Page<Customer>> iterator = pageIterable.iterator();<br />Page<Customer> page = iterator.next();<br />int count = page.count();<br /><br />// For a more concise approach, you can chain the method calls:<br />int count = customerTable.query(QueryEnhancedRequest.builder()<br />                .queryConditional(queryConditional)<br />                .select(Select.COUNT)<br />                .build())<br />            .iterator().next().count();</pre>  | 
|  Get a count of matching items of a scan **DynamoDB operation:**`Scan` with `Select.COUNT`  |  <pre>mapper.count(Customer.class, scanExpression)</pre>  |  <pre>// Get the count from scan results.<br />PageIterable<Customer> pageIterable =<br />    customerTable.scan(ScanEnhancedRequest.builder()<br />        .filterExpression(filterExpression)<br />        .select(Select.COUNT)<br />        .build());<br />Iterator<Page<Customer>> iterator = pageIterable.iterator();<br />Page<Customer> page = iterator.next();<br />int count = page.count();<br /><br />// For a more concise approach, you can chain the method calls:<br />int count = customerTable.scan(ScanEnhancedRequest.builder()<br />                .filterExpression(filterExpression)<br />                .select(Select.COUNT)<br />                .build())<br />            .iterator().next().count();</pre>  | 
|  Create a table in DynamoDB corresponding to the POJO class **DynamoDB operation:** `CreateTable`  |  <pre>mapper.generateCreateTableRequest(Customer.class)</pre> The previous statement generates a low-level create table request; users must call `createTable` on the DynamoDB client.  |  <pre>table.createTable(createTableRequest)<br /><br />table.createTable(r -> r.provisionedThroughput(defaultThroughput())<br />    .globalSecondaryIndices(<br />        EnhancedGlobalSecondaryIndex.builder()<br />            .indexName("gsi_1")<br />            .projection(p -> p.projectionType(ProjectionType.ALL))<br />            .provisionedThroughput(defaultThroughput())<br />            .build()));</pre>  | 
|  Perform a parallel scan in DynamoDB **DynamoDB operation:** `Scan` with `Segment` and `TotalSegments` parameters  |  <pre>mapper.parallelScan(Customer.class, <br />                    scanExpression, <br />                    numTotalSegments)</pre>  |  Users are required to handle the worker threads and call `scan` for each segment: <pre>table.scan(r -> r.segment(0).totalSegments(5))</pre>  | 
|  Integrate Amazon S3 with DynamoDB to store intelligent S3 links  |  <pre>mapper.createS3Link(bucket, key)<br />mapper.getS3ClientCache()</pre>  |  Not supported because it couples Amazon S3 and DynamoDB.  | 

## Map classes and properties
<a name="dynamodb-mapping-schemas"></a>

In both V1 and V2, you map classes to tables using bean-style annotations. V2 also offers [other ways to define schemas](ddb-en-client-adv-features.md#ddb-en-client-adv-features-schm-overview) for specific use cases, such as working with immutable classes.

### Bean annotations
<a name="dynamodb-mapping-schemas-annos"></a>

The following table shows the equivalent bean annotations for a specific use case that are used in V1 and V2. A `Customer` class scenario is used to illustrate parameters.

Annotations—as well as classes and enumerations—in V2 follow camel case convention and use 'DynamoDb', not 'DynamoDB'.


| Use case | V1 | V2 | 
| --- | --- | --- | 
| Map class to table |  <pre>@DynamoDBTable (tableName ="CustomerTable")</pre>  | <pre>@DynamoDbBean<br />@DynamoDbBean(converterProviders = {...})</pre>The table name is defined when calling the DynamoDbEnhancedClient\$1table() method. | 
| Designate a class member as a table attribute  |  <pre>@DynamoDBAttribute(attributeName = "customerName")</pre>  |  <pre>@DynamoDbAttribute("customerName") </pre>  | 
| Designate a class member is a hash/partition key |  <pre>@DynamoDBHashKey </pre>  |  <pre>@DynamoDbPartitionKey</pre>  | 
| Designate a class member is a range/sort key |  <pre>@DynamoDBRangeKey </pre>  |  <pre>@DynamoDbSortKey </pre>  | 
| Designate a class member is a secondary index hash/partition key |  <pre>@DynamoDBIndexHashKey </pre>  |  <pre>@DynamoDbSecondaryPartitionKey </pre>  | 
| Designate a class member is a secondary index range/sort key |  <pre>@DynamoDBIndexRangeKey </pre>  |  <pre>@DynamoDbSecondarySortKey </pre>  | 
| Ignore this class member when mapping to a table |  <pre>@DynamoDBIgnore </pre>  |  <pre>@DynamoDbIgnore</pre>  | 
| Designate a class member as an auto-generated UUID key attribute |  <pre>@DynamoDBAutoGeneratedKey</pre>  |  <pre>@DynamoDbAutoGeneratedUuid </pre> The extension that provides this is not loaded by default; you must add the extension to client builder.  | 
| Designate a class member as an auto-generated timestamp attribute |  <pre>@DynamoDBAutoGeneratedTimestamp</pre>  |  <pre>@DynamoDbAutoGeneratedTimestampAttribute</pre> The extension that provides this is not loaded by default; you must add the extension to client builder.  | 
| Designate a class member as an auto-incremented version attribute |  <pre>@DynamoDBVersionAttribute</pre>  |  <pre>@DynamoDbVersionAttribute</pre> The extension that provides this is auto-loaded.  | 
| Designate a class member as requiring a custom conversion |  <pre>@DynamoDBTypeConverted</pre>  |  <pre>@DynamoDbConvertedBy</pre>  | 
| Designate a class member to be stored as a different attribute type |  <pre>@DynamoDBTyped(<DynamoDBAttributeType>)</pre>  |  Use an `AttributeConverter` implementation. V2 provides many built-in converters for common Java types. You can also implement your own custom `AttributeConverter` or `AttributeConverterProvider`. See [Control attribute conversion](ddb-en-client-adv-features-conversion.md) in this guide.  | 
| Designate a class that can be serialized to a DynamoDB document (JSON-style document) or sub-document  |  <pre>@DynamoDBDocument</pre>  | Use the Enhanced Document API. See the following resources:[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-mapping-api-changes.html) | 

### V2 additional annotations
<a name="dynamodb-mapping-schemas-annos-v2-addnl"></a>


| Use case | V1 | V2 | 
| --- | --- | --- | 
| Designate a class member not to be stored as a NULL attribute if the Java value is null | N/A |  <pre>@DynamoDbIgnoreNulls</pre>  | 
| Designate a class member to be an empty object if all attributes are null | N/A |  <pre>@DynamoDbPreserveEmptyObject</pre>  | 
| Designate special update action for a class member | N/A |  <pre>@DynamoDbUpdateBehavior</pre>  | 
| Designate an immutable class | N/A |  <pre>@DynamoDbImmutable</pre>  | 
| Designate a class member as an auto-incremented counter attribute | N/A |  <pre>@DynamoDbAtomicCounter</pre> The extension that provides this functionality is auto-loaded.  | 

## Configuration
<a name="dynamodb-mapping-configuration"></a>

In V1, you generally control specific behaviors by using an instance of `DynamoDBMapperConfig`. You can supply the configuration object either when you create the mapper or when you make a request. In V2, configuration is specific to the request object for the operation.


| Use case | V1 | Default in V1 | V2 | 
| --- | --- | --- | --- | 
|  |  <pre>DynamoDBMapperConfig.builder()</pre>  |  |  | 
| Batch load/write retry strategy |  <pre>  .withBatchLoadRetryStrategy(loadRetryStrategy)</pre> <pre>  .withBatchWriteRetryStrategy(writeRetryStrategy)</pre>  | retry failed items | Configure the retry strategy on the underlying DynamoDBClient. See [Configure retry behavior in the AWS SDK for Java 2.x](retry-strategy.md) in this guide. | 
| Consistent reads |  <pre>  .withConsistentReads(CONSISTENT)</pre>  | EVENTUAL | By default, consistent reads is false for read operations. Override with .consistentRead(true) on the request object. | 
| Conversion schema with sets of marshallers/unmarshallers |  <pre>  .withConversionSchema(conversionSchema)</pre> Static implementations provide backwards compatibility with older versions.  | V2\$1COMPATIBLE | Not applicable. This is a legacy feature that refers to how the earliest versions of DynamoDB (V1) stored data types, and this behavior will not be preserved in the enhanced client. An example of behavior in DynamoDB V1 is storing booleans as Number instead of as Boolean. | 
| Table names |  <pre>  .withObjectTableNameResolver()<br />  .withTableNameOverride() <br />  .withTableNameResolver()</pre> Static implementations provide backwards compatibility with older versions  | use annotation or guess from class |  The table name is defined when calling the `DynamoDbEnhancedClient#table()` method.  | 
| Pagination load strategy |  <pre>  .withPaginationLoadingStrategy(strategy)</pre>  Options are: LAZY\$1`LOADING`, `EAGER_LOADING`, or `ITERATION_ONLY`  | LAZY\$1LOADING |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-mapping-api-changes.html)  | 
| Request metric collection |  <pre>  .withRequestMetricCollector(collector)</pre>  | null | Use metricPublisher() in ClientOverrideConfiguration when building the standard DynamoDB client.  | 
| Save behavior |  <pre>  .withSaveBehavior(SaveBehavior.CLOBBER) </pre> Options are `UPDATE`, `CLOBBER`, `PUT`, `APPEND_SET`, or `UPDATE_SKIP_NULL_ATTRIBUTES`.  | UPDATE |  In V2, you call `putItem()` or `updateItem()` explicitly. `CLOBBER or PUT`: Corresponding action in v 2 is calling `putItem()`. There is no specific `CLOBBER` configuration. `UPDATE`: Corresponds to `updateItem()` `UPDATE_SKIP_NULL_ATTRIBUTES`: Corresponds to `updateItem()`. Control update behavior with the request setting `ignoreNulls` and the annotation/tag `DynamoDbUpdateBehavior`. `APPEND_SET`: Not supported  | 
| Type converter factory |  <pre>  .withTypeConverterFactory(typeConverterFactory) </pre>  | standard type converters |  Set on the bean by using <pre>@DynamoDbBean(converterProviders = {ConverterProvider.class, <br />        DefaultAttributeConverterProvider.class})</pre>  | 

### Per-operation configuration
<a name="dynamodb-mapping-configuration-per-op"></a>

In V1, some operations, such as `query()`, are highly configurable through an “expression” object submitted to the operation. For example:

```
DynamoDBQueryExpression<Customer> emailBwQueryExpr = new DynamoDBQueryExpression<Customer>()
    .withRangeKeyCondition("Email",
        new Condition()
            .withComparisonOperator(ComparisonOperator.BEGINS_WITH)
            .withAttributeValueList(
                new AttributeValue().withS("my")));

mapper.query(Customer.class, emailBwQueryExpr);
```

In V2, instead of using a configuration object, you set parameters on the request object by using a builder. For example:

```
QueryEnhancedRequest emailBw = QueryEnhancedRequest.builder()
    .queryConditional(QueryConditional
        .sortBeginsWith(kb -> kb
            .sortValue("my"))).build();

customerTable.query(emailBw);
```

## Conditionals
<a name="dynamodb-mapping-conditionals"></a>

In V2, conditional and filtering expressions are expressed using an `Expression` object, which encapsulates the condition and the mapping of names and filters. 


| Use case | Operations | V1 | V2 | 
| --- | --- | --- | --- | 
| Expected attribute conditions | save(), delete(), query(), scan() |  <pre>new DynamoDBSaveExpression()<br />  .withExpected(Collections.singletonMap(<br />      "otherAttribute", new ExpectedAttributeValue(false)))<br />  .withConditionalOperator(ConditionalOperator.AND);</pre>  | Deprecated; use ConditionExpression instead. | 
| Condition expression | delete() |  <pre>deleteExpression.setConditionExpression("zipcode = :zipcode")<br />deleteExpression.setExpressionAttributeValues(...)<br /></pre>  |  <pre>Expression conditionExpression =<br />    Expression.builder()<br />        .expression("#key = :value OR #key1 = :value1")<br />        .putExpressionName("#key", "attribute")<br />        .putExpressionName("#key1", "attribute3")<br />        .putExpressionValue(":value", AttributeValues.stringValue("wrong"))<br />        .putExpressionValue(":value1", AttributeValues.stringValue("three"))<br />        .build();<br /><br />DeleteItemEnhancedRequest request = DeleteItemEnhancedRequest.builder()<br />         .conditionExpression(conditionExpression).build();</pre>  | 
| Filter expression | query(), scan() |  <pre>scanExpression<br />  .withFilterExpression("#statename = :state")<br />  .withExpressionAttributeValues(attributeValueMapBuilder.build())<br />  .withExpressionAttributeNames(attributeNameMapBuilder.build())<br /></pre>  |  <pre>Map<String, AttributeValue> values = singletonMap(":key", stringValue("value"));<br />Expression filterExpression =<br />    Expression.builder()<br />        .expression("name = :key")<br />        .expressionValues(values)<br />        .build();<br />QueryEnhancedRequest request = QueryEnhancedRequest.builder()<br />    .filterExpression(filterExpression).build();<br /></pre>  | 
| Condition expression for query | query() |  <pre>queryExpression.withKeyConditionExpression()</pre>  |  <pre>QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b<br />                .partitionValue("movie01"));<br /><br />QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder()<br />                .queryConditional(keyEqual)<br />                .build();</pre>  | 

## Type conversion
<a name="dynamodb-mapping-type-conv"></a>

### Default converters
<a name="dynamodb-mapping-type-conv-defaults"></a>

In V2, the SDK provides a set of default converters for all common types. You can change type converters both at the overall provider level as well as for a single attribute. You can find a list of the available converters in the [AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html) API reference.

### Set a custom converter for an attribute
<a name="dynamodb-mapping-type-conv-anno"></a>

In V1, you can annotate a getter method with `@DynamoDBTypeConverted` to specify the class that converts between the Java attribute type and a DynamoDB attribute type. For instance, a `CurrencyFormatConverter` that converts between a Java `Currency` type and DynamoDB String can be applied as shown in the following snippet.

```
@DynamoDBTypeConverted(converter = CurrencyFormatConverter.class)
public Currency getCurrency() { return currency; }
```

The V2 equivalent of the previous snippet is shown below.

```
@DynamoDbConvertedBy(CurrencyFormatConverter.class)
public Currency getCurrency() { return currency; }
```

**Note**  
In V1, you can apply the annotation to the attribute itself , a type or a user-defined annotation, V2 supports applying the annotation it only to the getter.

### Add a type converter factory or provider
<a name="dynamodb-mapping-type-conv-factory"></a>

In V1, you can provide your own set of type converters, or override the types you care about by adding a type converter factory to the configuration. The type converter factory extends `DynamoDBTypeConverterFactory`, and overrides are done by getting a reference to the default set and extending it. The following snippet demonstrates how to do this.

```
DynamoDBTypeConverterFactory typeConverterFactory =
    DynamoDBTypeConverterFactory.standard().override()
        .with(String.class, CustomBoolean.class, new DynamoDBTypeConverter<String, CustomBoolean>() {
            @Override
            public String convert(CustomBoolean bool) {
                return String.valueOf(bool.getValue());
            }
            @Override
            public CustomBoolean unconvert(String string) {
                return new CustomBoolean(Boolean.valueOf(string));
            }}).build();
DynamoDBMapperConfig config =
    DynamoDBMapperConfig.builder()
        .withTypeConverterFactory(typeConverterFactory)
        .build();
DynamoDBMapper mapperWithTypeConverterFactory = new DynamoDBMapper(dynamo, config);
```

V2 provides similar functionality through the `@DynamoDbBean` annotation. You can provide a single `AttributeConverterProvider` or a chain of ordered `AttributeConverterProvider`s. Note that if you supply your own chain of attribute converter providers, you will override the default converter provider and must include it in the chain to use its attribute converters. 

```
@DynamoDbBean(converterProviders = {
   ConverterProvider1.class, 
   ConverterProvider2.class,
   DefaultAttributeConverterProvider.class})
public class Customer {
  ...
}
```

The section on [attribute conversion](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example) in this guide contains a complete example for V2.

# String handling differences between version 1 and version 2 of the SDK for Java
<a name="dynamodb-migration-string-handling"></a>

V1 and V2 handle empty strings differently when sending data to DynamoDB:
+ **V1**: Converts empty strings to null values before sending to DynamoDB (resulting in no attribute)
+ **V2**: Sends empty strings as actual empty string values to DynamoDB

**Important**  
After migrating to V2, if you don't want empty strings stored in DynamoDB, you must implement custom converters. Without custom converters, V2 stores empty strings as actual empty string attributes in your DynamoDB items, which differs from V1's behavior of omitting these attributes entirely.

**Example custom converter for V2 that converts an empty string attribute to null**  

```
/**
 * Custom converter that maintains V1 behavior by converting empty strings to null values
 * when writing to DynamoDB, ensuring compatibility with existing data. No attribute will be saved to DynamoDB.
 */
public class NullifyEmptyStringConverter implements AttributeConverter<String> {
    @Override
    public AttributeValue transformFrom(String value) {
        if (value == null || value.isEmpty()) {
            return AttributeValue.builder().nul(true).build();
        }
        return AttributeValue.builder().s(value).build();
    }

    @Override
    public String transformTo(AttributeValue attributeValue) {
        if (attributeValue.nul()) {
            return null;
        }
        return attributeValue.s();
    }

    @Override
    public EnhancedType<String> type() {
        return EnhancedType.of(String.class);
    }

    @Override
    public AttributeValueType attributeValueType() {
        return AttributeValueType.S;
    }
}

// V2 usage:
@DynamoDbBean
public class Customer {
    private String name;

    @DynamoDbConvertedBy(NullifyEmptyStringConverter.class)
    public String getName() {
        return name;
    }
}
```



# Optimistic locking differences between version 1 and version 2 of the SDK for Java
<a name="dynamodb-migrate-optimstic-locking"></a>

Both V1 and V2 implement optimistic locking with an attribute annotation that marks one property on your bean class to store the version number.


**Differences in optimistic locking behavior**  

|  | V1 | V2 | 
| --- | --- | --- | 
| Bean class annotation | @DynamoDBVersionAttribute | @DynamoDbVersionAttribute (note that V2 uses a lowercase "b") | 
| Initial save | Version number attribute set to 1. |  The starting value for the version attribute set with `@DynamoDbVersionAttribute(startAt = X)`. Default value is 0.  | 
| Update | The version number attribute is incremented by 1 if the conditional check verifies that the version number of the object being updated matches the number in the database. |  The version number attribute is incremented if the conditional check verifies that the version number of the object being updated matches the number in the database. The version number attribute incremented by the `incrementBy` option set with `@DynamoDbVersionAttribute(incrementBy = X)`. Default value is 1.  | 
| Delete | DynamoDBMapper adds a conditional check that the version number of the object being deleted matches the version number in the database. |  V2 does not does not automatically add conditions for the delete operations. You must add condition expressions manually if you want to control the delete behavior. In the following example `recordVersion` is the bean's version attribute. <pre>// 1. Read the item and get its current version.<br />Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());<br />AttributeValue currentVersion = item.getRecordVersion();<br /><br />// 2. Create conditional delete with the `currentVersion` value.<br />DeleteItemEnhancedRequest deleteItemRequest =<br />    DeleteItemEnhancedRequest.builder()<br />       .key(KEY)<br />       .conditionExpression(Expression.builder()<br />           .expression("recordVersion = :current_version_value")<br />           .putExpressionValue(":current_version_value", currentVersion)<br />           .build()).build();<br /><br />customerTable.deleteItem(deleteItemRequest);</pre>  | 
| Transactional Write with a Condition Check | You cannot use a bean class that is annotated with @DynamoDBVersionAttribute in an addConditionCheck method. | You can use a bean class with the @DynamoDbVersionAttribute annotation in an addConditionCheck builder method for a transactWriteItems request. | 
| Disable | Disable optimistic locking by changing the DynamoDBMapperConfig.SaveBehavior enumeration value from UPDATE to CLOBBER. |  Do not use the `@DynamoDbVersionAttribute` annotation.  | 

# Fluent setters differences between version 1 and version 2 of the SDK for Java
<a name="dynamodb-migrate-fluent-setters"></a>

You can use POJOs with fluent setters in the DynamoDB mapping API for V1 and with V2 since version 2.30.29. 

For example, the following POJO returns a `Customer` instance from the `setName` method:

```
// V1

@DynamoDBTable(tableName ="Customer")
public class Customer{
  private String name;
  // Other attributes and methods not shown.
  public Customer setName(String name){
     this.name = name;
     return this;
  }
}
```

However, if you use a version of V2 prior to 2.30.29, `setName` returns a `Customer` instance with a `name` value of `null`.

```
// V2 prior to version 2.30.29.

@DynamoDbBean
public class Customer{
  private String name;
  // Other attributes and methods not shown.
  public Customer setName(String name){ 
     this.name = name;
     return this;  // Bug: returns this instance with a `name` value of `null`.
  }
}
```

```
// Available in V2 since version 2.30.29.

@DynamoDbBean
public class Customer{
  private String name;
  // Other attributes and methods not shown.
  public Customer setName(String name){ 
     this.name = name;
     return this;  // Returns this instance for method chaining with the `name` value set.
  }
}
```

# Document API differences between version 1 and version 2 of the AWS SDK for Java
<a name="dynamodb-mapping-document-api"></a>

The Document API supports working with JSON-style documents as single items in a DynamoDB table. The V1 Document API has a corresponding API in V2, but instead of using a separate client for the document API as in V1, V2 incorporates document API features in the DynamoDB enhanced client. 

In V1, the [https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/Item.html](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/Item.html) class represents an unstructured record from a DynamoDB table. In V2, an unstructured record is represented by an instance of the [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html) class. Note that primary keys are defined in the table schema for V2, and on the item itself in V1.

The table below compares the differences between the Document APIs in V1 and V2.


| Use case | V1 | V2 | 
| --- |--- |--- |
| Create a document client |  <pre>AmazonDynamoDB client = ... //Create a client.<br />DynamoDB documentClient = new DynamoDB(client);</pre>  |  <pre>// The V2 Document API uses the same DynamoDbEnhancedClient<br />// that is used for mapping POJOs.<br />DynamoDbClient standardClient = ... //Create a standard client.<br />DynamoDbEnhancedClient enhancedClient = ... // Create an enhanced client.<br /></pre>  | 
| Reference a table |  <pre>Table documentTable = docClient.documentClient("Person");</pre>  |  <pre>DynamoDbTable<EnhancedDocument> documentTable = enhancedClient.table("Person",  <br />        TableSchema.documentSchemaBuilder()<br />              .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)<br />              .attributeConverterProviders(AttributeConverterProvider.defaultProvider())<br />              .build());</pre>  | 
| **Work with semi-structured data** | 
| --- |
| Put item |  <pre>Item item = new Item()<br />      .withPrimaryKey("id", 50)<br />      .withString("firstName", "Shirley");<br />PutItemOutcome outcome = documentTable.putItem(item);</pre>  |  <pre>EnhancedDocument personDocument = EnhancedDocument.builder()<br />        .putNumber("id", 50)<br />        .putString("firstName", "Shirley")<br />        .build();<br />documentTable.putItem(personDocument);</pre>  | 
| Get item |  <pre>GetItemOutcome outcome = documentTable.getItemOutcome( "id", 50);<br />Item personDocFromDb = outcome.getItem();<br />String firstName = personDocFromDb.getString("firstName");<br /></pre>  |  <pre>EnhancedDocument personDocFromDb = documentTable<br />        .getItem(Key.builder()<br />            .partitionValue(50)<br />            .build()); <br />String firstName = personDocFromDb.getString("firstName");<br /></pre>  | 
| **Work with JSON items** | 
| --- |
| Convert a JSON structure to use it with the Document API |  <pre>// The 'jsonPerson' identifier is a JSON string. <br />Item item = new Item().fromJSON(jsonPerson);</pre>  |  <pre>// The 'jsonPerson' identifier is a JSON string.<br />EnhancedDocument document = EnhancedDocument.builder()<br />        .json(jsonPerson).build());</pre>  | 
| Put JSON |  <pre>documentTable.putItem(item)</pre>  |  <pre>documentTable.putItem(document);</pre>  | 
| Read JSON |  <pre>GetItemOutcome outcome = //Get item.<br />String jsonPerson = outcome.getItem().toJSON();</pre>  |  <pre>String jsonPerson = documentTable.getItem(Key.builder()<br />        .partitionValue(50).build())<br />        .fromJson();</pre>  | 

## API reference and guides for document APIs
<a name="dynamodb-mapping-document-api-ref"></a>


|  | V1 | V2 | 
| --- | --- | --- | 
| API reference | [API reference](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/package-summary.html) | [API reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) | 
| Documentation guide | [Amazon DynamoDB Developer Guide](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaDocumentAPIItemCRUD.html) | [Enhanced Document API](ddb-en-client-doc-api.md) (this guide) | 

# V1 Xpec API to V2 Expressions API
<a name="ddb-v1-xspec-migrate"></a>

The Expression Specification (Xspec) API available in V1 that helps create expressions to work with document-oriented data is not available in V2. V2 uses the Expression API, which works with both document-oriented data and object-to-item mapped data.


****  

|  | V1 | V2 | 
| --- | --- | --- | 
| API name | Expression Specification (Xspec) API | Expression API | 
| Works with | Methods of the Document API [Table](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/Table.html) class such as [updateItem](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/Table.html#updateItem-java.lang.String-java.lang.Object-com.amazonaws.services.dynamodbv2.xspec.UpdateItemExpressionSpec-) and [scan](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/Table.html#scan-com.amazonaws.services.dynamodbv2.xspec.ScanExpressionSpec-) |  Both APIs of DynamoDB Enhanced Client: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-v1-xspec-migrate.html) For both types of APIs, after you have acquired a [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) instance: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-v1-xspec-migrate.html) you use expressions in `DynamoDbTable` methods when you create request objects. For example in the `filterExpression` method of the [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.Builder.html)  | 
| Resources |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-v1-xspec-migrate.html)  | [Expressions information](ddb-en-client-expressions.md) in this Java Developer Guide | 

# Encryption library migration
<a name="ddb-encryption-lib-migrate"></a>

For information about migrating the encryption library for DynamoDB to work with V2 of the Java SDK, see the [Amazon DynamoDB Encryption Client Developer Guide](https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/ddb-java-migrate.html).