Skip to content

Releases: awspring/spring-data-dynamodb

Release list

1.0.0-ALPHA

1.0.0-ALPHA Pre-release
Pre-release

Choose a tag to compare

@MatejNedic MatejNedic released this 03 Sep 22:48

Spring 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.

📗 Reference Documentation

🚀 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 @Query expressions — 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

  • @Table and @Embedded annotations — model multiple entity types in a single physical table with prefix-based routing using startsWith, endsWith, or regex patterns
  • Secondary Index Views — type-safe, read-only @SecondaryIndex views over GSIs and LSIs that automatically set IndexName
  • 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 @SortKeyTemplate and 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 @ItemCollectionView for grouping heterogeneous rows from a partition into a single typed aggregate

DynamoDB-Native Features

  • Keyset pagination — Window<T> pagination backed by DynamoDB's LastEvaluatedKey cursor (no offset-based pagination)
  • Optimistic locking — @Version-based conditional writes for safe concurrent updates
  • Batch operations — saveAll() uses BatchWriteItem, automatically chunked at 25 items with retry logic for unprocessed items
  • Template API — DynamoDbOperations and DynamoDbTemplate for operations beyond repository methods
  • Typed query builder — IndexQueryBuilder for validated, fluent queries with automatic attribute name aliasing

Developer Experience

  • Event callbacks — before/after save, convert, update, and delete events with @EventListener and functional callback interfaces
  • Custom conversions — register custom converters for domain-specific types or use per-property @ValueConverter
  • Flexible configuration — override AbstractDynamoDbConfiguration for 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:4566

Standalone 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_tournam...
Read more