DynamoDB Streams: ExpiredIteratorException

TL;DR — A shard iterator from GetShardIterator is only valid for 15 minutes. If your consumer paused, processed a batch slowly, or held an iterator across a restart, the next GetRecords call throws this. Recover by requesting a new iterator — use AFTER_SEQUENCE_NUMBER with the last sequence number you processed so you resume exactly where you left off.

What it means

ExpiredIteratorException: The provided iterator exceeds the maximum age allowed.

Reading a DynamoDB stream directly is a two-step loop: GetShardIterator gives you a position handle into a shard, and each GetRecords call consumes it and returns the next one (NextShardIterator). Every one of those handles expires 15 minutes after it's issued. The exception doesn't mean data is gone — stream records live for 24 hours — it only means this position handle is stale and you need a new one.

Why it happens

  • Slow processing between polls — the work done per batch (writes, external calls) takes longer than 15 minutes before the next GetRecords.
  • A paused or suspended consumer — a stopped worker, a long deploy, or a debugger session holding the loop.
  • Storing iterators — persisting the iterator string (in a DB or checkpoint) and reusing it later; iterators are ephemeral, sequence numbers are not.
  • Backoff gone long — an error-retry loop that slept past the window.

How to fix it

  1. Catch it and re-seed the iterator from your last checkpoint — track the SequenceNumber of the last record you processed, then:

    const {ShardIterator} = await streams.send(
      new GetShardIteratorCommand({
        StreamArn: streamArn,
        ShardId: shardId,
        ShardIteratorType: 'AFTER_SEQUENCE_NUMBER',
        SequenceNumber: lastProcessedSequenceNumber
      })
    );

    No checkpoint yet? TRIM_HORIZON reprocesses the shard from the oldest available record; LATEST skips to new activity.

  2. Checkpoint sequence numbers, never iterators — the sequence number is the durable resume point.

  3. Keep the poll loop tight — do heavy per-record work asynchronously (queue it) so the GetRecords cadence stays well inside 15 minutes.

  4. Prefer managed consumers — Lambda event source mappings and the DynamoDB Streams Kinesis Adapter (KCL) manage iterators and checkpoints for you; hand-rolled GetRecords loops are where this exception lives.

Make sure your handling is idempotent: resuming from a checkpoint can redeliver a record you half-processed. The stream itself contains each record exactly once, but a consumer that re-seeds from its last checkpoint effectively processes at-least-once.

Debugging what a stream actually emitted is much easier when you can see the items changing — the DynoTable desktop app shows an item's live state so you can match stream records against the data. Estimating the read cost of a catch-up replay? The DynamoDB pricing calculator has you covered.

References

Last verified 2026-07-13 against the official AWS documentation linked above.

Work with DynamoDB without the Console

DynoTable is a fast desktop client for DynamoDB — browse tables, run SQL-style queries, and edit items locally.