View a markdown version of this page

Generate a schema from annotations - AWS SDK for Kotlin

Generate a schema from annotations

The simplest way to use DynamoDB Mapper is to annotate your Kotlin classes and let the SDK generate their schemas for you at build time. You annotate a class with @DynamoDbItem, mark its key properties, and the schema-generator Gradle plugin inspects the annotated classes and emits a schema object and a convenience extension function for obtaining a typed table.

This topic covers the plugin setup, the available annotations, custom converters for unsupported types, and how to configure the generator. For the complete list of annotations and their parameters, see the annotations reference.

Note

The schema-generator plugin is available for Gradle only. If you use Maven, see Manually define schemas for how to define schemas in code.

How annotation-based generation works

  1. You apply the schema-generator plugin and annotate your data classes.

  2. At build time, the plugin’s symbol processor reads the annotations. For each @DynamoDbItem class Foo, the plugin generates a FooSchema object and a DynamoDbMapper.getFooTable(…​) extension function.

  3. Your code calls the generated extension or passes the generated schema to getTable to obtain a typed Table and perform operations on it.

You never need to write or edit the generated code. Rebuilding your project regenerates it from your annotated classes.

Add the plugin and dependencies

Apply the plugin and add the runtime and annotations dependencies in your build.gradle.kts. Replace X.Y.Z with the latest release of the SDK.

// build.gradle.kts val sdkVersion = "[.replaceable]##X.Y.Z##" plugins { id("aws.sdk.kotlin.hll.dynamodbmapper.schema.generator") version sdkVersion } dependencies { implementation("aws.sdk.kotlin:dynamodb-mapper:$sdkVersion") implementation("aws.sdk.kotlin:dynamodb-mapper-annotations:$sdkVersion") }

Each of these dependencies fulfills a different function:

  • The dynamodbmapper.schema.generator plugin generates the schemas

  • The dynamodb-mapper dependency provides the DynamoDbMapper type

  • The dynamodb-mapper-annotations dependency provides the annotations you apply to your classes

Annotate a class

Annotate the class with @DynamoDbItem and mark its primary key. Every top-level item type must have exactly one partition key (@DynamoDbPartitionKey) and can have at most one sort key (@DynamoDbSortKey). All other public properties are mapped to attributes automatically.

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 @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>, val placedAt: Instant, )

The generator maps the property types it knows about, including primitives, String, enums such as OrderStatus, collections, and several SDK runtime types such as Instant. For types it doesn’t support out of the box, supply a custom converter.

Supported types

The following Kotlin types are automatically converted by DynamoDB Mapper into the given DynamoDB types:

Kotlin type DynamoDB type Notes

Boolean

BOOL (Boolean)

Byte

N (Number)

ByteArray

B (Binary)

Char

S (String)

CharArray

S (String)

Document.Map (aws.smithy.kotlin.runtime.content)

M (Map)

Values in the map use the converter appropriate for their type

Double

N (Number)

Enum<E> (any enum class)

S (String)

Stored as the enum constant’s name

Float

N (Number)

Instant (aws.smithy.kotlin.runtime.time)

N (Number)

Stored as epoch seconds by default

Int

N (Number)

List<E>

L (List)

Elements in the list use the converter appropriate for their type

Long

N (Number)

Map<String, V>

M (Map)

Values in the map use the converter appropriate for their type

Set<Byte>

NS (Number Set)

Set<ByteArray>

BS (Binary Set)

Set<Char>

SS (String Set)

Set<CharArray>

SS (String Set)

Set<Double>

NS (Number Set)

Set<Float>

NS (Number Set)

Set<Int>

NS (Number Set)

Set<Long>

NS (Number Set)

Set<Short>

NS (Number Set)

Set<String>

SS (String Set)

Set<UByte>

NS (Number Set)

Set<UInt>

NS (Number Set)

Set<ULong>

NS (Number Set)

Set<UShort>

NS (Number Set)

Short

N (Number)

String

S (String)

UByte

N (Number)

UInt

N (Number)

ULong

N (Number)

UShort

N (Number)

Url (aws.smithy.kotlin.runtime.net.url)

S (String)

Any supported type T?

NULL when the value is null; otherwise uses the converter for T

Customize how properties map

Several property-level annotations can adjust the default mapping. For example:

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 }

In alphabetical order, the attribute-level annotations used in the preceding example are:

  • @DynamoDbAttribute(name): store the property under a different DynamoDB attribute name (here, placedAt is stored as created_at). Without it, the attribute name matches the property name.

  • @DynamoDbAttributeConverter(converter): supply a custom ValueConverter for a property whose type the generator doesn’t support on its own. See Convert unsupported types.

  • @DynamoDbIgnore: exclude a property from mapping entirely (here, a computed convenience property).

More property annotations enable various features and are covered in-depth in DynamoDB Mapper annotations reference.

Convert unsupported types

For a property whose type the generator doesn’t natively map (for example, kotlin.uuid.Uuid), apply @DynamoDbAttributeConverter with a ValueConverter that translates between your type and a DynamoDB attribute value. A value converter implements two methods: convertRight (your type → attribute value) and convertLeft (attribute value → your type).

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()) }

The class passed to @DynamoDbAttributeConverter(…​) must implement ValueConverter. See Manually define schemas for the full converter model and the built-in converters you can reuse.

Use the generated schema

After you build the project, the generator produces an OrderSchema object and, by default, a getOrderTable extension on DynamoDbMapper. Note that the extension name contains the class name Order, while the string you pass is your actual table name "orders":

val ordersTable = mapper.getOrderTable("orders")

Equivalently, pass the generated schema to getTable. By default, the generated schema lives in a package derived from your class’s package plus .dynamodbmapper.generatedschemas. For instance, if your annotated class is com.example.store.model.Order then the default generated schema is com.example.store.model.dynamodbmapper.generatedschemas.OrderSchema:

import com.example.store.model.dynamodbmapper.generatedschemas.OrderSchema val ordersTable = mapper.getTable("orders", OrderSchema)

Configure the generator

The plugin contributes a dynamoDbMapper extension to your build script. All settings are optional; their defaults are shown in the following example.

// build.gradle.kts import aws.sdk.kotlin.hll.codegen.rendering.Visibility import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.DestinationPackage import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.GenerateBuilderClasses dynamoDbMapper { // When to generate builder classes for your item types: WHEN_REQUIRED (default) or ALWAYS. generateBuilderClasses = GenerateBuilderClasses.WHEN_REQUIRED // Visibility of generated declarations. Default: PUBLIC. visibility = Visibility.PUBLIC // Where generated code is placed. Relative(...) (default) appends to each class's own package; // Absolute(...) places everything in one fixed package. destinationPackage = DestinationPackage.Relative("dynamodbmapper.generatedschemas") // Whether to generate the DynamoDbMapper.get<Class>Table() convenience extensions. Default: true. generateGetTableExtension = true }

The configurable settings, in alphabetical order:

Setting Type Default Purpose

destinationPackage

DestinationPackage

DestinationPackage.Relative("dynamodbmapper.generatedschemas")

Package for generated code. Use DestinationPackage.Relative(suffix) to place it relative to each source class’s package, or DestinationPackage.Absolute(pkg) to use one fixed package.

generateBuilderClasses

GenerateBuilderClasses

WHEN_REQUIRED

WHEN_REQUIRED generates a builder only when a class can’t be built directly (for example, it has immutable members and no zero-arg constructor); ALWAYS always generates one.

generateGetTableExtension

Boolean

true

Whether to generate the DynamoDbMapper.get<Class>Table(…​) extensions. When false, obtain tables with getTable(name, schema).

visibility

Visibility

PUBLIC

Visibility of generated declarations.