Saga Retention

Completed sagas are kept as tombstones so a late or duplicate event is skipped idempotently rather than resurrecting a finished saga. Retention removes those tombstones once they are old enough to no longer serve that purpose.

Retention is off by default: it is active once a max-age is set, for every saga type or for one. There is no default max-age. It deletes completed sagas whose completion is older than that age, on a schedule.

Enabling retention

spring:
  ddd:
    saga:
      retention:
        max-age: 30d        # delete completed sagas older than 30 days

With only a max-age, retention runs every hour.

A short max-age (for example 30s) approximates delete-on-completion while still keeping the idempotency tombstone for the grace window.

Scheduling

Choose either a fixed delay or a cron expression — setting both is rejected at startup.

Property Behaviour

schedule.enabled

Whether this instance runs retention. true by default; see How retention runs.

schedule.interval

Fixed delay between runs (for example 1h, the default). Used when schedule.cron is not set.

schedule.cron

Spring 6-field cron expression (for example 0 0 3 * * * for 03:00 daily). - (the default) or an empty string means not set. When set, it drives retention and schedule.interval must be left unset. It must match a time in the future.

chunk-size

How many completed sagas one delete statement removes; a run deletes chunk by chunk until a chunk is not full. From 1 to 1000, 100 by default.

Cron is the better fit for a maintenance job — it lets you target an off-peak window instead of an arbitrary wall-clock time.

spring:
  ddd:
    saga:
      retention:
        max-age: 30d
        schedule:
          cron: "0 0 3 * * *"   # 03:00 every day

Per-saga max age

A saga type can keep its completed sagas longer or shorter than the global max-age. Name the type under spring.ddd.sagas, keyed by its @Saga(type) id; a type without an entry uses the global max-age. Retention is active as soon as any type has a max-age, so the global one may be left out and only the types that need retention set it.

spring:
  ddd:
    saga:
      retention:
        max-age: 30d
    sagas:
      overdraft-saga:
        retention:
          max-age: 7d          # keep overdraft sagas for 7 days
      transfer-saga:
        retention:
          max-age: 365d        # keep transfer sagas for a year

A type cannot be exempted from a global max-age, only given a longer one. A key under spring.ddd.sagas that names no saga type fails the startup, and a saga type with a dot in its id is written in brackets, "[payment.saga]". The same entry overrides the consumer settings of that type; see Event delivery.

How retention runs

Every instance whose schedule.enabled is on runs retention on the schedule; retention takes no lease. Two instances can delete at the same time without harm: each completed saga is deleted once, and a run that finds nothing left deletes nothing. Set schedule.enabled: false to limit retention to the instances you choose.

An application that declares its own SagaStore and sets a max-age fails the startup unless it also supplies its own SagaRetentionStore.

For the full spring.ddd.saga.retention property table, see Configuration properties.

  • Overview — concept, delivery model, lifecycle, starters.

  • Writing a saga — @Saga, @SagaEventHandler, handler parameters, worked example.

  • Storage & tables — @SagaTable, JSON persistence, when a saga ended, schema creation.

  • Deadlines & timeouts — scheduling and cancelling deadlines from within a handler.

  • Event delivery — the topics a saga reads, one consumer per saga type, delivery guarantees.