Get started with DynamoDB Mapper
This tutorial introduces the basic components of DynamoDB Mapper and shows how to use it in your code. The examples use an online store domain whose flagship type is an Order.
Add dependencies
Add the DynamoDB Mapper dependencies to your project build file. Replace X.Y.Z with the latest release of the SDK
Example
Create and use a mapper
DynamoDB Mapper uses the SDK’s DynamoDB client to interact with DynamoDB. Provide a configured DynamoDbClient when you create a mapper:
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbMapper import aws.sdk.kotlin.services.dynamodb.DynamoDbClient val client = DynamoDbClient.fromEnvironment() val mapper = DynamoDbMapper(client)
Note
DynamoDB Mapper doesn’t create tables. Use the DynamoDbClient to create tables and indexes.
Define a schema with class annotations
For many Kotlin classes, the SDK can generate a schema at build time using the DynamoDB Mapper schema generator plugin. The plugin inspects your annotated classes and emits the schema, which removes the boilerplate of defining schemas by hand.
Annotate your class with @DynamoDbItem, mark the partition key with @DynamoDbPartitionKey, and (for a composite key) mark the sort key with @DynamoDbSortKey:
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttributeConverter import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbIgnore import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbSortKey import aws.smithy.kotlin.runtime.time.Instant import kotlin.uuid.Uuid @DynamoDbItem data class Order( @DynamoDbPartitionKey val customerId: String, @DynamoDbSortKey val orderId: String, val status: OrderStatus, val totalCents: Long, val productSkus: List<String>, val tags: Set<String>, @DynamoDbAttribute("created_at") val placedAt: Instant, @DynamoDbAttributeConverter(UuidConverter::class) val idempotencyKey: Uuid, ) { @DynamoDbIgnore val isLargeOrder: Boolean get() = totalCents >= 100_00 } enum class OrderStatus { PENDING, PAID, SHIPPED, DELIVERED, CANCELED }
This example previews a few field-level annotations that will be discussed in greater detail later:
-
@DynamoDbAttributerenames an attribute -
@DynamoDbAttributeConvertersupplies a custom converter for a type the generator doesn’t support on its own (here,kotlin.uuid.Uuid) -
@DynamoDbIgnoreexcludes a property from mapping
See Generate a schema from annotations and the annotations reference for the full set.
The custom converter is a small object that implements convertRight/convertLeft:
import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter import aws.sdk.kotlin.services.dynamodb.model.AttributeValue import kotlin.uuid.Uuid object UuidConverter : ValueConverter<Uuid> { override fun convertRight(from: Uuid): AttributeValue = AttributeValue.S(from.toString()) override fun convertLeft(from: AttributeValue): Uuid = Uuid.parse(from.asS()) }
After you build the project, the generator produces an OrderSchema and a convenience extension function. You can get a table reference with the generated getOrderTable extension. Note that the function name contains the class name (Order), while the string you pass is your actual table name (orders):
val ordersTable = mapper.getOrderTable("orders")
Equivalently, you can pass the generated schema to getTable:
import com.example.store.model.dynamodbmapper.generatedschemas.OrderSchema val ordersTable = mapper.getTable("orders", OrderSchema)
Invoke operations
After you have a table reference, you can perform operations on it. The following sections show a few basics. For the complete operation surface and the different ways to invoke each operation, see the Operations overview.
Put an item
import aws.smithy.kotlin.runtime.time.Instant import kotlin.uuid.Uuid ordersTable.putItem { item = Order( customerId = "customer-123", orderId = "ORDER#2026-06-25#0042", status = OrderStatus.PENDING, totalCents = 4_999, productSkus = listOf("SKU-1", "SKU-2"), tags = setOf("gift"), placedAt = Instant.now(), idempotencyKey = Uuid.random(), ) }
Get an item
getItem returns a GetItemResponse; read the mapped object from its item property (which is null if no matching item exists). For a composite-key table, supply both keys, wrapping each key value with Key(…):
import aws.sdk.kotlin.hll.dynamodbmapper.items.Key val response = ordersTable.getItem { partitionKey = Key("customer-123") sortKey = Key("ORDER#2026-06-25#0042") } println(response.item) // the Order, or null
Query with paginated results
query and scan can match more items than fit in a single response. DynamoDB Mapper provides paginating variants (queryPaginated and scanPaginated) that return a Flow
import aws.sdk.kotlin.hll.dynamodbmapper.expressions.KeyFilter val responses = ordersTable.queryPaginated { keyCondition = KeyFilter(partitionKey = "customer-123") } responses.collect { response -> val orders = response.items.orEmpty() println("Found a page of ${orders.size} orders") orders.forEach { order -> println(order) } }
Often a flow of objects is more convenient than a flow of response pages. Call items() to flatten a paginated flow into a Flow of your objects (here, a Flow<Order> instead of a Flow<QueryResponse<Order>>):
val orders = ordersTable .queryPaginated { keyCondition = KeyFilter(partitionKey = "customer-123") limit = 20 } .items() orders.collect { order -> println(order) }
Next steps
-
Learn the full operation surface in the Operations overview.
-
Customize schema generation in Generate a schema from annotations.
-
Define schemas by hand in Manually define schemas.