Repository navigation
1.0.0-ALPHA
Pre-releaseSpring Data DynamoDB 1.0.0-ALPHA Release Notes
What's New
Spring Data DynamoDB 1.0.0-ALPHA is the first official release of Spring Data support for Amazon DynamoDB. This module brings the familiar Spring Data programming model to DynamoDB with first-class support for single-table design patterns.
π Why Spring Data DynamoDB?
DynamoDB's power lies in its ability to model multiple entity types within a single physical table, enabling efficient access patterns through strategic use of partition keys, sort keys, and secondary indexes. Traditional ORM approaches often obscure this design philosophy.
Spring Data DynamoDB embraces DynamoDB's core principles:
- Not an ORM β DynamoDB's
(partition key, sort key)model is closer to Cassandra than to traditional document stores; this library makes that modeling explicit and readable - Single-table design first β store multiple entity types in one table with typed repositories for each view
- Access pattern driven β model your data around how you query it, not how you store it
- Type-safe secondary indexes β read-only views over GSIs and LSIs with compile-time guarantees
β¨ Key Features
Repository Support
- Spring Data Repository abstractions β familiar CRUD operations and query derivation for DynamoDB
- Derived query methods β generate queries from method names:
findByPkAndSkStartingWith(...) - Explicit
@Queryexpressions β write DynamoDB key condition and filter expressions when derivation isn't enough - PartiQL support β execute PartiQL statements directly from repository methods
- Named queries β define reusable queries in properties files for better maintainability
Single-Table Design Features
@Tableand@Embeddedannotations β model multiple entity types in a single physical table with prefix-based routing usingstartsWith,endsWith, orregexpatterns- Secondary Index Views β type-safe, read-only
@SecondaryIndexviews over GSIs and LSIs that automatically setIndexName - Multi-attribute GSI keys β support for DynamoDB's multi-key GSI feature (up to 4 partition attributes and 4 sort attributes) with explicit ordering
- Sort Key Templates β compose sort keys from multiple properties with
@SortKeyTemplateand decompose them automatically on read - Polymorphic containers β store and retrieve different entity types from the same partition key with type-safe routing
- Item Collection Views β read-only
@ItemCollectionViewfor grouping heterogeneous rows from a partition into a single typed aggregate
DynamoDB-Native Features
- Keyset pagination β
Window<T>pagination backed by DynamoDB'sLastEvaluatedKeycursor (no offset-based pagination) - Optimistic locking β
@Version-based conditional writes for safe concurrent updates - Batch operations β
saveAll()usesBatchWriteItem, automatically chunked at 25 items with retry logic for unprocessed items - Template API β
DynamoDbOperationsandDynamoDbTemplatefor operations beyond repository methods - Typed query builder β
IndexQueryBuilderfor validated, fluent queries with automatic attribute name aliasing
Developer Experience
- Event callbacks β before/after save, convert, update, and delete events with
@EventListenerand functional callback interfaces - Custom conversions β register custom converters for domain-specific types or use per-property
@ValueConverter - Flexible configuration β override
AbstractDynamoDbConfigurationfor full control - AWS SDK v2 β built on the modern AWS SDK for Java 2.x
- Spring Cloud AWS integration β seamless auto-configuration when using Spring Cloud AWS starters
- Fail-fast validation β invalid queries, scan methods without
@AllowScan, and incorrect entity mappings fail at application startup, not at runtime
π¦ Compatibility
| Component | Version |
|---|---|
| Spring Boot | 4.1.x |
| Spring Framework | 7.0.x |
| Spring Data Commons | 4.1.x |
| AWS Java SDK | 2.x |
| Java | 17+ |
π― Getting Started
Add the dependency:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-data-dynamodb</artifactId>
<version>1.0.0-ALPHA</version>
</dependency>With Spring Cloud AWS (recommended)
Add the DynamoDB starter:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-dynamodb</artifactId>
</dependency>Configure the module:
@Configuration
@EnableDynamoDbRepositories(basePackageClasses = TournamentRepository.class)
public class DynamoDbConfig extends AbstractDynamoDbConfiguration {
}Configure the client through properties:
spring.cloud.aws.region.static=eu-central-1
# Optional: LocalStack or custom endpoint
spring.cloud.aws.dynamodb.endpoint=http://localhost:4566Standalone Configuration
@Configuration
@EnableDynamoDbRepositories(basePackageClasses = TournamentRepository.class)
public class DynamoDbConfig extends AbstractDynamoDbConfiguration {
@Bean
@Override
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder()
.region(Region.EU_CENTRAL_1)
.build();
}
}Define an entity:
@Table(tableName = "tournament_arena")
public class Tournament {
@PartitionKey
private String pk;
@SortKey
private String sk;
private String name;
private String season;
}Create a repository:
public interface TournamentRepository extends DynamoDbRepository<Tournament, String> {
List<Tournament> findByPk(String pk);
List<Tournament> findByPkAndSkStartingWith(String pk, String prefix);
}π Example: Single-Table Design
One table, multiple entity types:
// Base table entity with composed sort key
@Table(tableName = "tournament_arena")
@SortKeyTemplate("MATCH#{matchDate}#{matchId}")
public class Match {
@PartitionKey private String pk; // "TOURNAMENT#winter2026"
private LocalDate matchDate; // 2026-02-01
private String matchId; // "m1"
// Physical sk: "MATCH#2026-02-01#m1"
private String region;
}
// GSI view for querying by player
@SecondaryIndex(name = "GSI1", tableName = "tournament_arena")
public class PlayerMatchesView {
@PartitionKey @Column("gsi1pk") private String collectionKey; // "PLAYER#p1"
@SortKey @Column("gsi1sk") private String itemKey; // "MATCH#m1"
private String region;
}
// Multi-attribute GSI with native composite keys
@SecondaryIndex("by_tournament_region")
public class MatchesByTournamentRegionView {
@PartitionKey(order = 0) private String tournamentId;
@PartitionKey(order = 1) private String region;
@SortKey(order = 0) private String round; // most general
@SortKey(order = 1) private String bracket;
@SortKey(order = 2) private String matchId; // most specific
}Polymorphic single-table container:
@Table(tableName = "tournament_arena")
public class ArenaItem {
@PartitionKey @Column("pk") private String pk;
@SortKey @Column("sk") private String sk;
@Column("gsi1pk") private String gsi1pk;
@Column("gsi1sk") private String gsi1sk;
@Embedded(startsWith = "TOURNAMENT#") private TournamentData tournament;
@Embedded(startsWith = "PLAYER#") private PlayerData player;
@Embedded(startsWith = "MATCH#") private MatchData match;
@Embedded(startsWith = "RESULT#") private ResultData result;
}Item collection view for grouped reads:
@ItemCollectionView(tableName = "single_table_demo", partitionKey = "pk", sortKey = "sk")
public class CustomerRow {
@ItemCollectionMember(regex = "ORDER#[^#]+")
private OrderData order;
@ItemCollectionMember(regex = "ORDER#[^#]+#LINE#[^#]+")
private List<OrderLineData> line;
}
public interface CustomerRepository extends ItemCollectionRepository<CustomerRow> {
}Type-safe queries:
// Query base table
List<Match> matches = matchRepository.findByPk("TOURNAMENT#winter2026");
// Query GSI automatically β no indexName needed
List<PlayerMatchesView> playerMatches =
playerMatchesRepository.findByCollectionKey("PLAYER#p1");
// Multi-attribute GSI query with derived method
List<MatchesByTournamentRegionView> matches =
repository.findByTournamentIdAndRegionAndRound("winter2026", "NA-EAST", "SEMIFINALS");
// Explicit query with BETWEEN
@Query(keyConditionExpression = "#pk = :pk AND #sk BETWEEN :from AND :to",
names = { @ExpressionName(name = "#pk", value = "gsi1pk"),
@ExpressionName(name = "#sk", value = "gsi1sk") })
List<MatchByDate> findInDateRange(@Param("pk") String pk,
@Param("from") String from,
@Param("to") String to);Keyset pagination:
Window<PlayerMatchesView> page = repository.findWindowByCollectionKey(
"PLAYER#p1", ScrollPosition.keyset(), Limit.of(25));
if (page.hasNext()) {
ScrollPosition next = page.positionAt(page.size() - 1);
Window<PlayerMatchesView> more =
repository.findWindowByCollectionKey("PLAYER#p1", next, Limit.of(25));
}Template API and IndexQueryBuilder:
// Fluent, validated query builder
EntityQueryResult<List<MatchesByTournamentRegionView>> result =
operations.query(MatchesByTournamentRegionView.class, "by_tournament_region")
.partition("tournamentId", "winter2026")
.partition("region", "NA-EAST")
.sortEq("round", "SEMIFINALS")
.sortBeginsWith("bracket", "UP")
.limit(50)
.execute();
// Direct operations
operations.save(match);
operations.saveAll(List.of(m1, m2, m3)); // BatchWriteItem
operations.findById("TOURNAMENT#winter2026", "MATCH#m1", Match.class);Single-item updates:
@Update(updateExpression = "SET #winner = :winner",
conditionExpression = "attribute_exists(#pk)",
names = { @ExpressionName(name = "#winner", value = "winner"),
@ExpressionName(name = "#pk", value = "pk") })
void recordWinner(@Param("pk") String pk, @Param("sk") String sk,
@Param("winner") String winner);π§ What's Not Included
This module is intentionally focused on data access patterns:
- β No table creation β use CDK, Terraform, CloudFormation, or the AWS Console
- β No schema migration β DynamoDB is schema-less; indexes are infrastructure
- β No
Page<T>orSlice<T>β DynamoDB cursors don't support offset-based pagination; useWindow<T>instead - β No automatic scanning β methods requiring a full-table scan must be explicitly annotated with
@AllowScan - β No transaction support β transactional operations are planned for a future release
π‘οΈ Fail-Fast Validation
Spring Data DynamoDB validates repository methods and entity mappings at application startup, not at runtime:
- Invalid derived queries fail immediately if they don't match DynamoDB's key condition rules
- Scan queries without
@AllowScanare rejected to prevent accidental full-table scans - Entities with incorrect key mappings (multiple partition keys, incompatible annotations) fail at bootstrap
- Invalid index names and key schema mismatches are caught before any query executes
- Unsupported return types (
Page<T>,Slice<T>) on repository methods are rejected during initialization
π Core Annotations Reference
| Annotation | Purpose |
|---|---|
@Table |
Marks a base-table entity with physical table name |
@SecondaryIndex |
Marks a read-only GSI/LSI view with index name |
@PartitionKey |
Partition-key component (supports order for multi-attribute keys) |
@SortKey |
Sort-key component (supports order for multi-attribute keys) |
@Column |
Maps property to a physical attribute name |
@Embedded |
Flattens a nested object with startsWith/endsWith/regex routing |
@ItemCollectionView |
Read-only view grouping partition rows into a single aggregate |
@ItemCollectionMember |
Routes rows to item-collection fields by sort-key pattern |
@SortKeyTemplate |
Composes sort key from multiple properties; decomposes on read |
@Derived |
Property reconstructed from template, never written to DynamoDB |
@Version |
Optimistic locking with conditional writes |
@Query |
Explicit key condition, filter, or PartiQL expression |
@Update |
Single-item update with update and condition expressions |
@AllowScan |
Explicit opt-in for full-table scan methods |
@ValueConverter |
Per-property custom conversion |
π Author
Matej Nedic (@MatejNedic)
π Resources
π License
Apache License 2.0
Full Changelog: https://github.com/awspring/spring-data-dynamodb/commits/v1.0.0-ALPHA