Skip to main content
The @DeadLetterQueue annotation enables automatic routing of failed events to a Dead Letter Queue backend. When a handler throws an exception — and all retries are exhausted if @RetryPolicy is present — the event is persisted in the DLQ instead of being silently lost. The annotation itself is backend-agnostic: it carries only the cross-cutting policy (retention, payload flags) that every DLQ backend can honour. Backend-specific tuning (e.g. the MongoDB collection name) lives on a companion annotation such as @MongoDlqOptions.

Attributes

AttributeTypeDefaultDescription
enabledbooleantrueWhether DLQ is enabled
retentionDaysint30Retention in days. 0 means permanent (no expiry)
includeOriginalDocumentbooleantrueWhether to include the original document in the DLQ entry
includeStackTracebooleantrueWhether to include the full stack trace in the DLQ entry

retentionDays

How long DLQ entries are retained before automatic cleanup. Each backend translates this to its native mechanism — the MongoDB backend sets a TTL index on the expiresAt field; Kafka maps it to retention.ms; RabbitMQ to the x-message-ttl header; JDBC to a scheduled cleanup job. Set to 0 to keep entries permanently.
// Keep failed events for 90 days
@DeadLetterQueue(retentionDays = 90)

// Keep failed events permanently (no auto-cleanup)
@DeadLetterQueue(retentionDays = 0)

includeOriginalDocument and includeStackTrace

These control what data is captured in the DLQ entry. Disabling them can reduce storage for high-throughput streams where you only need the error metadata.
// Minimal DLQ entry — only metadata, no document or stack trace
@DeadLetterQueue(includeOriginalDocument = false, includeStackTrace = false)

Mongo-specific options: @MongoDlqOptions

@DeadLetterQueue deliberately does not carry the MongoDB collection name. Routing a stream’s failed events to a custom collection is a backend-specific concern, declared on the companion annotation @MongoDlqOptions:
@ChangeStream(collection = "orders")
@DeadLetterQueue(retentionDays = 90)
@MongoDlqOptions(collection = "orders_dlq")
public class OrderHandler { ... }
AttributeTypeDefaultDescription
collectionString""MongoDB collection for this stream’s DLQ entries. Empty means use the global default.
The global default for the collection name is configured by the flowwarden.dlq.mongo.collection property (defaults to _fw_dlq). Streams without @MongoDlqOptions write to that collection.
@MongoDlqOptions has no effect when the configured DlqStore implementation is non-Mongo. Each backend ships its own companion annotation.
The MongoDB TTL index on expiresAt is created automatically at startup for every collection bound to a @DeadLetterQueue-annotated stream, so retentionDays takes effect without any manual index setup.

Storage

Failed events are persisted via the DlqStore SPI. The MongoDB-backed implementation (MongoDlqStore) is auto-configured by default and creates the expiresAt TTL index automatically at startup. Custom backends (Kafka, RabbitMQ, JDBC, …) plug in by declaring a @Bean DlqStore. The Redis satellite does not ship a DlqStore — Redis-backed deployments either keep the Mongo DLQ or supply a custom bean.

Roadmap

The following attributes are planned but not yet implemented:
AttributeDescriptionStatus
reprocessStrategyAutomatic reprocessing strategy (MANUAL, AUTO_CRON)Planned
reprocessCronCron expression for automatic reprocessingPlanned
reprocessBatchSizeBatch size for automatic reprocessingPlanned
mongoTemplateRefCustom MongoTemplate bean reference for multi-datasource setups (on @MongoDlqOptions)Planned

See Also

Retry & DLQ Guide

Understand DLQ routing, manual sends, document schema, and best practices

@RetryPolicy

Configure exponential backoff retry for failed handlers

@Checkpoint

Resume token persistence for reliable stream recovery

ChangeStreamContext

Runtime context including sendToDlq(), attempt number, and more