AWS Developer Tools Blog

Introducing immutable class mapping for the Enhanced DynamoDB Client in AWS SDK for Java 2.x

We are pleased to announce that the enhanced DynamoDB client in the AWS SDK for Java 2.x now supports the mapping of immutable Java objects directly with records in your DynamoDB tables. Previously, only mutable ‘Java bean’ style objects were supported.

Immutability in Java is a commonly used style that allows developers to create classes that are side-effect free and therefore predictable in complex and multi-threaded applications. Immutable objects in Java characteristically only have getters (and no setters) and are constructed by passing all their properties at once to the constructor. It is also typical to have a separate mutable ‘builder’ class to assist with collecting the initialization values for these properties.

Before this release, customers using immutable style objects had no way to directly map records they were reading and writing from their DynamoDB tables to these objects. Instead, they were forced to work with bean objects and use elaborate workarounds, such as copying and transforming the objects read by the enhanced client or wrapping them in an opaque container. Now the enhanced client can work directly with the immutable classes with the addition of a just a few simple annotations.

Quick walk-through for mapping an immutable class

Prerequisites:

Before getting started with mapping immutable classes, ensure your SDK dependency is up to date with all the latest released bug-fixes and features. For immutable object mapping support you must be using version 2.14.10 or later. See our Java SDK Developer Guide for the details on how to manage the AWS Java SDK dependency in your project.

Step 1 : Annotate your existing immutable class

First, create and annotate an immutable class that will map to records in your DynamoDB table. You may either follow the example below or create your own class. The immutable class you create should only have getters, and there must be a static builder class that can be used to construct instances of it.

Add the @DynamoDbImmutable annotation to the immutable class. You must pass a parameter of ‘builder’ to the annotation with the value set to the builder class you use to build objects of your immutable class. An example using an inner-class of our immutable class ‘Record’ called ‘Builder’ would be @DynamoDbImmutable(builder = Record.Builder.class).

Add other standard DynamoDB enhanced client annotations as required. These annotations are the same and work in the same way as the existing bean class annotations. At a minimum you must specify a @DynamoDbPrimaryKey. Other annotations are optional.

This example shows a complete but minimally annotated immutable class that is ready for use with the enhanced client:

@DynamoDbImmutable(builder = Record.Builder.class)
public class Record {
  private final String id;
  private final String attribute;

  private Record(Builder b) {
    this.id = b.id;
    this.attribute = b.attribute;
  }

  public static Builder builder() {
    return new Builder();
  }

  @DynamoDbPrimaryKey
  public String id() { return this.id; }

  public String attribute() { return this.attribute; }

  public static final class Builder {
    private String id;
    private String attribute;

    public Builder id(String id) {
      this.id = id;
      return this;
    }

    public Builder attribute(String attribute) {
      this.attribute = attribute;
      return this;
    }

    public Record build() {
      return new Record(this);
    }
  }
}

For more information about what structurally qualifies a class to be used as a DynamoDB annotated immutable class, please refer to “Working with immutable data classes” section in the DynamoDB enhanced client guide.

Step 2 : Construct a TableSchema for your immutable class

Next, create a TableSchema object for the immutable class you created in the previous step. A TableSchema is an object that defines the structure and attributes of DynamoDB records and knows how to map them to a Java class.

To keep things simple, this example uses a new static constructor method in TableSchema that auto-detects and works with both annotated immutable classes and annotated bean classes:

TableSchema Record recordTableSchema = tableSchema.forClass(Record.class);

Step 3 : Create a DynamoDbTable resource object

Next, associate a real DynamoDB table with your TableSchema to create a DynamoDBTable resource object to execute commands against. This example uses a real DynamoDB table named ‘my_table’:

DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();
DynamoDbTable<Record> recordTable = enhancedClient.table("my_table", recordTableSchema);

Step 4 : Execute commands against the DynamoDbTable resource object

Any objects read from the database will now be created as immutable objects using the class you provided. You may also pass immutable instances of this class to operations that write or update records in the table. For example:

Record newRecord = Record.builder().id("123").attribute("foo").build();
recordTable.putItem(newRecord);

Record key = Record.builder().id("123").build();
Record persistedRecord = recordTable.getItem(key);

Conclusion

In this blog post we showed you how to set-up and begin mapping immutable classes with the DynamoDB enhanced client. The enhanced client is open-source and resides in the same repository as the AWS SDK for Java 2.0.

We hope you’ll find this new feature useful. You can always share your feedback on our GitHub issues page or up-vote other ideas for features you’d also like to see in the DynamoDB enhanced client or the Java SDK in general.