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
Catch it and re-seed the iterator from your last checkpoint — track the
SequenceNumberof 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_HORIZONreprocesses the shard from the oldest available record;LATESTskips to new activity.Checkpoint sequence numbers, never iterators — the sequence number is the durable resume point.
Keep the poll loop tight — do heavy per-record work asynchronously (queue it) so the
GetRecordscadence stays well inside 15 minutes.Prefer managed consumers — Lambda event source mappings and the DynamoDB Streams Kinesis Adapter (KCL) manage iterators and checkpoints for you; hand-rolled
GetRecordsloops 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.
Related errors
- TrimmedDataAccessException — you fell behind the 24-hour retention window (data actually gone).
- Stream not enabled
- Invalid StreamArn
- Learn: DynamoDB Streams
References
- GetShardIterator — Amazon DynamoDB Streams API Reference
- GetRecords — Amazon DynamoDB Streams API Reference
- Change data capture for DynamoDB Streams — Amazon DynamoDB Developer Guide
Last verified 2026-07-13 against the official AWS documentation linked above.