Dead Letter Queues in Kafka Connect
What happens when one bad record threatens to stop a whole data pipeline? Learn how Kafka Connect sets troublesome records aside in a special topic, how to configure it, how to watch it, and how to fix and replay what landed there.
Imagine a factory that packs thousands of parcels every hour on a moving conveyor belt. Every parcel has a label. One day, a parcel arrives with a smudged label that nobody can read. The belt worker has three choices. She can stop the entire belt until someone fixes the label. She can throw the parcel in the bin and carry on, hoping it was not important. Or she can put the parcel in a special tray marked “Problems”, let the belt keep running, and let a specialist sort it out later.
The third choice is the one that keeps factories running and customers happy. In Kafka Connect, that special tray has a name: the dead letter queue, often shortened to DLQ. In Kafka it is not a separate product. It is simply an ordinary Kafka topic where Connect places records that it could not process.
This guide explains the whole idea from the ground up. We will start with what Kafka Connect is, then look at the ways records can go wrong, then see exactly how a dead letter queue catches them. Along the way you will learn the settings, the hidden details, and the habits that separate a calm production pipeline from a noisy one.
A dead letter queue in Kafka Connect is a Kafka topic that receives copies of records a sink connector failed to process, so the connector can keep running while people investigate the failed records later. It is turned on with a topic name setting, and it works only for sink connectors.
Why One Bad Record Can Hurt So Much
To see why dead letter queues exist, start with what goes wrong without one.
Kafka is patient, and that is the problem
A Kafka topic is a long notebook of records. A consumer reads it one line at a time, in order, and remembers its place using a number called an offset. If the consumer cannot understand line 5,000, it cannot simply jump to line 5,001 without making a decision. If it retries line 5,000 forever, it never moves on. Everything behind that line waits.
A record that a consumer can never process is often called a poison pill. It is poisonous because it does not only fail itself. It blocks all the healthy records queued behind it.
Think of a single-lane road with a broken-down truck. The truck is only one vehicle, but the hundreds of cars behind it cannot pass. A dead letter queue is like a tow lane that moves the truck aside so traffic flows again.
What makes a record bad?
Records fail for many ordinary reasons. Nobody has to do anything wrong on purpose.
Wrong shape
A producer sends plain text to a topic that the connector expects to hold structured JSON.
Unknown schema
A record refers to a schema that cannot be found, or does not match what the reader expects.
Broken bytes
The bytes are cut off or damaged, so they cannot be turned back into a proper record.
Failed transformation
A step that reshapes the record hits an empty value it did not expect.
Destination refuses it
The database or storage system rejects the record because it breaks a rule, such as a value that is too long.
Human mistake
Someone points the connector at the wrong topic, so every record looks bad.
Three ways to react
| Reaction | What happens | Good side | Risk |
|---|---|---|---|
| Stop everything | The connector task fails until a human fixes it | Nothing is lost or skipped quietly | The whole pipeline stalls for one record |
| Skip and forget | The bad record is dropped and processing continues | Pipeline keeps flowing | Silent data loss |
| Set aside in a DLQ | The bad record is saved in another topic and processing continues | Flow continues and nothing is lost | Someone must watch and handle the DLQ |
A dead letter queue is not magic. It moves the problem from “the pipeline is stuck” to “someone must look at the tray”. A DLQ that nobody watches is just a slower way to lose data.
The Vocabulary You Need First
A short list of words makes the rest of this article easy.
Kafka Connect
Kafka Connect is a tool that comes with Apache Kafka. Its job is to move data between Kafka and other systems, without you writing custom programs. You describe what you want in a small configuration, and Connect does the moving, the retrying, and the tracking of progress.
Source and sink
A source connector brings data into Kafka from somewhere else, for example from a database. A sink connector pushes data out of Kafka into somewhere else, for example into cloud storage or a search engine. This difference matters a lot in this article, because the dead letter queue feature is built for sinks.
Think of Kafka as a large warehouse. A source connector is the truck that delivers goods into the warehouse. A sink connector is the truck that takes goods out and delivers them to shops.
Worker, connector, and task
- A worker is a running program (a process) that hosts connectors. Several workers together form a Connect cluster.
- A connector is the named job you configure, such as “copy orders into cloud storage”.
- A task is one unit of work inside a connector. A connector can split its job into several tasks that run in parallel, often one per group of partitions.
Converter
Kafka stores records as raw bytes. A converter turns those bytes into a structured object that the connector understands, and the other way around. Common converters read JSON, Avro, or Protobuf. There is one converter for the record key, one for the value, and one for headers.
Single message transform
A single message transform, or SMT, is a small step that changes each record on its way through, such as renaming a field, dropping a field, or routing the record to a different topic. SMTs can be chained one after another.
Offset and commit
An offset is a record’s position number in its partition. Connect commits offsets, meaning it records “I have finished up to here”, so a restart continues from the right place.
| Word | Simple meaning |
|---|---|
| Sink connector | Moves data from Kafka to an outside system |
| Source connector | Moves data from an outside system into Kafka |
| Converter | Turns bytes into objects and back |
| SMT | A small per-record change step |
| Task | One parallel worker inside a connector |
| Dead letter queue | A Kafka topic that holds records that could not be processed |
The Big Picture: A Side Tray for Problem Records
Here is the whole idea in one picture and a few sentences.
Without a dead letter queue, a sink connector reads records from a topic and writes them to a destination. If a record fails, the connector has only two options: stop, or skip. With a dead letter queue, there is a third path. The failed record is copied into a separate topic, and the connector goes on to the next record.
flowchart LR
T["Source topic: orders"] --> C["Sink connector task"]
C -->|"good records"| D[("Destination: cloud storage or database")]
C -->|"bad records"| Q["Dead letter queue topic: dlq-orders-sink"]
Q --> H["Human or repair job investigates"]
The picture shows a source topic feeding a sink connector task. Healthy records go through to the destination. Records that fail are diverted into the dead letter queue topic. From there, a person or a repair job can look at them, fix the cause, and decide what to do next.
A post office does not stop the whole sorting line because one envelope has no address. That envelope goes into a “Return or investigate” bin, and the rest of the mail keeps moving.
Three key facts to remember
- It is opt-in. Nothing goes to a DLQ unless you name a topic in the configuration.
- It only works when errors are tolerated. You must also tell Connect to keep going after errors, otherwise the task still fails on the first bad record.
- It is just a topic. You can read it with any consumer, set retention on it, secure it, and monitor it like any other topic.
Real-world picture: loading orders into storage
A retail company copies its orders topic into cloud storage every few minutes for analytics. On a Tuesday, a new mobile app version starts sending a malformed order. Without a DLQ, the copy job stops, and analytics is stale until an engineer wakes up. With a DLQ, all good orders keep landing, and the single malformed order waits in a tray for someone to review.
Where Can a Record Fail Inside Connect?
A record passes through several stations. A problem at any one of them can trigger error handling.
The stations of a sink connector
flowchart LR
K["Kafka consumer reads raw bytes"] --> HC["Header converter"]
HC --> KC["Key converter"]
KC --> VC["Value converter"]
VC --> SMT["Transformations (SMTs)"]
SMT --> PUT["Connector writes to destination"]
A sink task first reads raw bytes with a normal Kafka consumer. Next come the converters for headers, key, and value. Then any transformations run in order. Finally the connector’s own code writes the record to the destination. Errors can occur at any of these stations.
| Station | Example failure | Covered by Connect’s error handling? |
|---|---|---|
| Consumer read | Network trouble talking to the broker | No, handled by the consumer’s own retry logic |
| Header, key, or value converter | Bytes are not valid JSON, or the schema is unknown | Yes |
| Transformation | A step meets a null it did not expect | Yes |
| Connector write (task logic) | Database rejects the row | Only if the connector reports it, or throws a retriable error |
Many beginners assume the DLQ catches every error a connector can have. It does not. It covers converter and transformation problems automatically. Errors inside the connector’s own write logic reach the DLQ only when that connector is written to report them. We cover that in a later chapter.
Source connectors have stations too
A source connector reads from an outside system, builds a record, runs transformations, and then uses converters to turn the record into bytes for Kafka. Errors in the transformation and converter stations can be tolerated, but there is no built-in dead letter queue for sources. Later we look at the practical alternatives.
Think of an airport baggage system. Bags pass a scanner, then a sorter, then a loader. If the scanner cannot read a tag, the airport’s safety net puts the bag aside. But if the loader damages a suitcase at the end, that is not something the scanner’s safety net can catch. Each station needs its own care.
The Default: Fail Fast and Stop
Before dead letter queues, let us understand what Connect does out of the box.
The setting called errors.tolerance
Connect has a setting named errors.tolerance. It has two values.
- none is the default. The first error that Connect cannot fix makes the task fail.
- all tells Connect to tolerate errors: skip the bad record and carry on.
What “fail” looks like
A bad record arrives
A converter or a transformation raises an error on record number 5,000.
The task stops
The task moves into a FAILED state and stops consuming.
Lag builds up
New records keep arriving in the topic, but nothing reads them. The gap between the newest record and the connector’s position grows.
A human is needed
Someone must read the log, understand the cause, fix it, and restart the task.
The same record may fail again
After the restart, the connector resumes at the same offset, meets the same bad record, and fails again, unless the cause was truly fixed.
This is the cautious factory manager who presses the big red stop button whenever one parcel looks odd. Nothing wrong ever ships, but the belt sits idle until someone walks over to investigate.
When fail-fast is the right choice
Stopping is not always bad. It is the safest option when every record matters absolutely and you want a human to look at the very first problem. Examples include early testing, brand-new pipelines, and financial feeds where a skipped record would be a serious issue. Fail-fast also protects you from a wider mistake, such as a wrong topic name, where every record is bad and continuing would just fill a DLQ with junk.
Strengths of fail-fast
- No record is skipped by accident
- Problems are noticed immediately
- Simple to reason about
- Prevents a flood of junk from a wrong configuration
Weaknesses of fail-fast
- One record halts the whole pipeline
- Recovery needs a human, even at night
- Lag and delay grow while it is stuck
- Restarting alone does not fix a real bad record
Skip and Continue: Tolerating All Errors
Setting tolerance to “all” keeps the pipeline moving, but the details are important.
What changes
With errors.tolerance set to all, a record that fails in a converter or transformation is no longer a reason to stop. Connect marks it as skipped and moves on to the next record. The task stays healthy, and lag stays low.
The silent data loss trap
Here is the dangerous part. If you set tolerance to “all” and do nothing else, the bad records are simply dropped. Nothing tells you. The task state says RUNNING. The dashboard looks green. Yet some percentage of your data never reached the destination.
Tolerance “all” without a dead letter queue and without logging is one of the most dangerous settings in Kafka Connect. It turns loud failures into silent data loss. Never use it alone in production.
Log the errors at least
Two settings help you see what is happening:
errors.log.enablewrites each error into the Connect worker’s log. The default is false.errors.log.include.messagesadds details of the failing record to that log, such as its topic, partition, and offset. The default is false, because record contents can be sensitive.
Logging is better than nothing, but it has a weakness: a log line is not the record. You know something failed, but the original bytes are not preserved in a form that you can replay. That is exactly the gap that a dead letter queue fills.
| Configuration | Behavior on a bad record | Can you recover the record? |
|---|---|---|
| Tolerance none (default) | Task fails | Yes, it is still in the source topic, but the pipeline is stuck |
| Tolerance all, no logging | Skipped silently | Only by re-reading the source topic, if you even know |
| Tolerance all, with logging | Skipped, error line logged | Only by looking up the offset in the source topic |
| Tolerance all, with DLQ | Skipped, copy saved in the DLQ topic | Yes, easily |
Tolerance “all” alone is like a shop assistant who quietly throws away any item with a torn barcode and never tells the manager. Adding a log is like asking her to write a note. Adding a DLQ is like giving her a box to put the torn items in, so the manager can fix them later.
Retries and Backoff: Trying Again Before Giving Up
Some errors are temporary. Connect can wait and try again before it decides a record is truly bad.
Why retry helps
Not every failure means the record is broken. Sometimes the record is fine and the world around it is having a bad moment. A converter may need to ask a schema registry for a schema, and the registry may be restarting. A destination may be briefly overloaded. If Connect gave up at once, it would send perfectly good records to the DLQ. Retrying gives temporary problems time to pass.
The two retry settings
errors.retry.timeout
The total time Connect keeps retrying a failed operation. The default is zero, which means no retries. A value of minus one means retry forever.
errors.retry.delay.max.ms
The longest wait between two attempts. Connect waits a little at first and increases the wait each time, up to this limit. The default is sixty seconds.
This pattern of waiting longer after each failure is called backoff. It avoids hammering a struggling service with a flood of requests.
You call a friend and the line is busy. You do not redial every second. You wait a minute, try again, then wait a few minutes, and so on. If the total waiting time passes your limit, you send a message instead. Retry timeout is your limit, and the DLQ is the message.
How retries and the DLQ work together
flowchart TD
A["Record fails at a station"] --> B{"Retry time left?"}
B -->|"yes"| C["Wait with backoff then try again"]
C --> D{"Worked this time?"}
D -->|"yes"| E["Continue normally"]
D -->|"no"| B
B -->|"no"| F{"Tolerance = all?"}
F -->|"no"| G["Task fails"]
F -->|"yes"| H{"DLQ configured?"}
H -->|"yes"| I["Send record to DLQ and continue"]
H -->|"no"| J["Skip record and continue"]
Follow the arrows. A failure first asks whether there is retry time left. If so, Connect waits and tries again. When time runs out, the next question is whether errors are tolerated. If not, the task fails. If yes, a configured DLQ receives the record; otherwise the record is skipped.
Retrying makes sense for outside dependencies such as a schema registry or a destination service. It does not help with a truly broken record, because that record will fail the same way each time. If your errors are mostly format problems, keep the retry timeout small.
The Dead Letter Queue Itself
Now we can finally look closely at the tray.
What it is
The dead letter queue is a plain Kafka topic. When a sink connector cannot process a record and errors are tolerated, Connect uses a Kafka producer to write a copy of the original record into that topic. The key and value are saved as the raw bytes that were originally read, so nothing has been changed or lost by the failed conversion.
The settings that control it
| Setting | What it does | Default |
|---|---|---|
| errors.tolerance | Must be set to all so the task continues after an error | none |
| errors.deadletterqueue.topic.name | Names the DLQ topic. Setting this turns the feature on | Not set |
| errors.deadletterqueue.topic.replication.factor | How many copies of the DLQ topic are kept if Connect creates it | 3 |
| errors.deadletterqueue.context.headers.enable | Adds headers that explain why and where the record failed | false |
| errors.retry.timeout | Total time to keep retrying before giving up | 0 |
| errors.retry.delay.max.ms | Longest wait between retries | 60000 |
| errors.log.enable | Also writes each error to the worker log | false |
| errors.log.include.messages | Includes record details in the log line | false |
Always turn on the context headers setting. Without headers, a record in the DLQ is just bytes with no explanation. With headers, each record tells you where it came from and why it failed, which saves hours of detective work.
A minimal mental picture of the configuration
A sink connector with a working dead letter queue needs just three ideas in its configuration: tolerate errors, name a DLQ topic, and add context headers. Optionally, you add logging and a small retry window. That is all. There is no extra service to install, and no separate product to buy.
Real-world picture: one DLQ per connector
Most teams give each sink connector its own DLQ topic, named after the connector, such as dlq-orders-storage-sink. This makes it obvious whose problem a record is, keeps volumes separate, and lets each team set retention and permissions on its own tray.
Context Headers: The Note Attached to Every Bad Record
A failed record with no explanation is a puzzle. Headers turn it into a clue.
What headers are
Every Kafka record can carry small labelled pieces of extra information called headers. They travel beside the key and value. When you turn on the context headers setting, Connect adds a set of headers to each record it writes into the DLQ. The names begin with __connect.errors, so they are easy to recognize.
What the headers tell you
| Information | Why it helps |
|---|---|
| Original topic | Tells you which topic the record came from |
| Original partition and offset | Lets you find the exact record in the source topic |
| Connector name and task ID | Shows which job and which parallel worker hit the error |
| Stage | Shows where it failed: a key, value, or header converter, a transformation, or the connector’s write step |
| Failing class name | Shows which converter or transform was running |
| Exception class and message | Gives the short reason, such as “unrecognized token” or “schema not found” |
| Stack trace | Gives the detailed technical trail for developers |
Imagine the “Problems” tray at a post office. A bare envelope in the tray tells you nothing. But if a sticky note says “found at sorting desk 3, at the address-reading step, reason: smudged ink”, you know exactly where to start. The headers are that sticky note.
Using the headers well
- Group by reason. Count how many DLQ records share the same exception message. Often one cause explains ninety percent of them.
- Group by origin. Count by original topic and partition to see whether only one producer or one partition is misbehaving.
- Trace back. Use the original topic, partition, and offset to look at the record in its source, along with the records around it.
- Watch the stage. A flood of converter errors usually means a format or schema problem. A flood of transformation errors points to a bug or an unexpected null in your own settings.
Stack traces and exception messages can contain fragments of the record’s contents, such as a customer name inside a parsing error. Treat the DLQ headers as sensitive data and protect the topic with the same care as the original topic.
Architecture and Components
Let us see every part involved when a bad record is caught.
flowchart TD
subgraph Kafka["Kafka Cluster"]
SRC["Source topic"]
DLQ["Dead letter topic"]
CFG["Connect internal topics: configs, offsets, status"]
end
subgraph Worker["Connect Worker Process"]
CON["Consumer"]
CNV["Converters"]
SMT["Transformations"]
SNK["Sink task logic"]
EH["Error handling: retry and tolerance"]
DP["DLQ producer"]
end
EXT[("External system")]
SRC --> CON
CON --> CNV
CNV --> SMT
SMT --> SNK
SNK --> EXT
CNV -. "error" .-> EH
SMT -. "error" .-> EH
SNK -. "reported error" .-> EH
EH --> DP
DP --> DLQ
The diagram has two big boxes. The Kafka cluster holds the source topic, the dead letter topic, and Connect’s own internal topics. The worker process holds a consumer, the converters, the transformations, and the sink task logic. When an error appears at any station, it flows into the error handling layer. That layer decides whether to retry, fail, skip, or hand the record to a DLQ producer, which writes it into the dead letter topic.
Consumer
A normal Kafka consumer pulls raw records from the source topic.
Converters
Turn bytes into structured objects. A frequent source of errors.
Transformations
Optional per-record steps. Another frequent source of errors.
Sink task logic
The connector code that delivers records to the destination.
Error handling layer
Applies retry time, tolerance, and logging rules to every failure.
DLQ producer
A Kafka producer that writes the original record and its headers into the DLQ topic.
Think of a quality-control inspector standing beside a production line. Each station can call the inspector when it sees a defect. The inspector decides whether to wait and retry, stop the line, discard the item, or place it in the “needs repair” bin along with a repair note.
Connect’s own internal topics
In distributed mode, Connect stores its own state in Kafka topics: one for connector configuration, one for source offsets, and one for status. These are separate from your DLQ. The DLQ is a data topic that you own, name, and manage, while the internal topics are managed by Connect.
Internal Working: The Journey of a Bad Record
Follow one broken record from arrival to rest in the DLQ.
The consumer polls a batch
The task’s consumer reads a batch of raw records. Among them is one whose value is not valid for the converter.
The converter fails
The value converter tries to decode the bytes and raises an exception.
The error is captured with context
Connect notes the stage, the exception, and the original consumer record with its topic, partition, and offset.
Retry rules are applied
If a retry timeout is set, Connect waits and tries again. If it still fails when time runs out, it moves on.
Tolerance is checked
If tolerance is none, the task fails here. If it is all, the flow continues.
Logging happens if enabled
A line is written to the worker log, with or without record details, depending on your settings.
The DLQ producer writes the record
The original key and value bytes are sent to the dead letter topic, with context headers if enabled.
The task continues
The failed record is dropped from the batch. The remaining good records go on to the destination, and offsets are committed as normal.
Batches and the write step
Sink tasks usually process records in batches. Converters and transformations run on each record one at a time, so a bad record is removed individually while the rest of the batch is delivered. The connector’s write step then receives only the healthy records.
The DLQ stores the original bytes as they were read from the source topic. This matters because a failed conversion means Connect never had a clean object. Keeping the raw bytes means you can inspect, repair, and replay exactly what the producer sent.
What happens to ordering?
When a record is diverted, the records after it still flow in their normal order. The bad record simply misses its turn. If you later repair and replay it, it arrives after newer records for the same key. For destinations that care about the newest value per key, replay carefully, and check whether a newer version has already been written.
A student’s exam paper is set aside because the name is unreadable. The teacher marks the other papers first. When the name is finally identified, that paper gets marked later, but it still counts. The order of marking changed, not the result, unless the results were being ranked in a way that depends on order.
Errors Inside the Connector: The Errant Record Reporter
Some of the most common problems happen at the last step, when the connector talks to the destination.
The original gap
When the dead letter queue feature first arrived, it covered errors in converters and transformations. But suppose your sink connector writes to a database and the database rejects one row because a text value is too long. That error happens inside the connector’s own code, after converters and transformations have succeeded. The basic error handling could not send such a record to the DLQ, because the connector did not tell Connect which record was at fault.
The fix: reporting bad records from the connector
Later versions of Kafka Connect added a feature for sink connector developers, known as the errant record reporter. It lets a connector say, “I could not process this particular record, and here is the reason.” Connect then applies the usual rules: retry, tolerate, log, and send the record to the DLQ. The connector can keep going with the rest of its batch.
flowchart LR
B["Batch of 100 records"] --> S["Sink connector code"]
S -->|"97 accepted"| DB[("Destination")]
S -->|"3 rejected, reported one by one"| R["Errant record reporter"]
R --> EH["Connect error handling"]
EH --> DLQ["Dead letter topic"]
In the picture, a batch of one hundred records goes to the sink connector. Ninety-seven are accepted by the destination. Three are rejected. The connector reports these three one by one through the reporter, and Connect sends them to the dead letter topic. The other ninety-seven are not held up.
Not every connector uses the reporter. Support depends on how the connector was written and which version you run. If a connector does not report errors this way, an error inside its write step may still fail the task even with a DLQ configured. Read the connector’s documentation and test with a deliberately bad record.
Connectors with their own error settings
Some connectors also provide their own settings for problems at the destination. For example, a search-engine sink may have a setting for how to treat documents the search engine refuses. These settings work alongside the general Connect error handling. Check which mode each connector supports, and prefer the ones that route bad records into your DLQ over the ones that quietly ignore them.
| Where the error happens | Caught by generic Connect error handling? | Reaches the DLQ? |
|---|---|---|
| Converter | Yes | Yes |
| Transformation | Yes | Yes |
| Connector write, connector uses the reporter | Yes, via the reporter | Yes |
| Connector write, connector does not use the reporter | Only if it throws a retriable error | Usually no; the task may fail |
What About Source Connectors?
The DLQ setting exists only for sink connectors, so sources need other plans.
Why there is no source DLQ
A dead letter queue puts the failed Kafka record into another Kafka topic. In a sink, the failed item is already a Kafka record, so saving a copy is natural. In a source, the failed item comes from outside Kafka, such as a database row or a file line. There is no Kafka record yet, and Connect has no general way to store the outside item as one.
For a source connector, the error handling settings still apply to converters and transformations, but the choices are limited to failing the task or skipping the item, with optional logging.
Practical alternatives
Pass raw data through
Read the outside data as plain text or bytes, without strict parsing. Do the careful parsing later in a place that can handle failures.
Two-stage pipeline
The source connector writes to a “raw” topic. A separate stream job parses records and routes bad ones to its own error topic.
Connector-specific handling
Some connectors offer their own way to record or report bad input. Check each connector’s documentation.
Fix at the origin
Add validation in the producing system, so bad data never reaches the connector.
flowchart LR
X[("Outside system")] --> SC["Source connector (light parsing)"]
SC --> RAW["Raw topic"]
RAW --> ST["Stream job validates and parses"]
ST -->|"good"| CLEAN["Clean topic"]
ST -->|"bad"| ERR["Error topic for this stage"]
The diagram shows a common pattern. The source connector only lifts data from the outside system with minimal parsing, so it rarely fails. A downstream stream processing job does the strict checking. Good records go to a clean topic, and bad ones go to an error topic, which plays the same role as a DLQ.
The habit of moving strict parsing away from the source connector and into a separate stage also helps you react quickly, because you can change the validation rules without touching the connector.
Data Flow and the Life of a DLQ Record
A record in the DLQ has a life story, and a good team knows every chapter.
flowchart LR
A["Record fails in Connect"] --> B["Stored in DLQ topic"]
B --> C["Alert fires: DLQ volume above normal"]
C --> D["Engineer triages using headers"]
D --> E{"Cause understood?"}
E -->|"data problem"| F["Repair the record"]
E -->|"config or code problem"| G["Fix the connector, converter, or schema"]
E -->|"truly junk"| H["Discard and record the reason"]
F --> I["Replay to the source topic or destination"]
G --> I
I --> J["DLQ record expires by retention"]
H --> J
The path begins when a record fails and lands in the DLQ. An alert tells the team that something arrived. The engineer reads the headers to understand the cause. There are three outcomes. The data may need repair. The pipeline itself may need a fix. Or the record may be truly junk, which is discarded with a note. Repaired or unblocked records are replayed. Eventually, retention removes old DLQ records.
Triage in five questions
How many records?
A handful may be isolated bad data. Thousands in minutes suggest a systemic problem.
Which stage failed?
Converter, transformation, or connector write. This tells you where to look.
What is the exception message?
Group by message. Usually there are only one or two distinct reasons.
Who produced them?
Look at the original topic and partition. Often one producer version is responsible.
Is it still happening?
If new records keep arriving, fix the cause before replaying, or the DLQ will refill.
Retention: the tray has a time limit
Like any topic, a DLQ deletes old records after its retention time. If your team takes a week to react and the retention is three days, the evidence disappears. Choose a retention long enough to cover holidays, weekends, and slow investigations. Many teams choose a couple of weeks for DLQ topics.
The source topic also has retention. If the source records expire before you investigate, the DLQ copy may be the only remaining copy. That is one more reason to alert quickly and keep DLQ retention generous.
Reprocessing: Getting Records Back Into the Pipeline
Saving bad records is only half the job. The other half is putting the fixed ones back.
Connect does not replay DLQ records for you. You choose a replay strategy that fits the cause of failure.
Common replay strategies
| Strategy | How it works | Best when |
|---|---|---|
| Re-send to the original topic | A small program reads the DLQ, repairs the record, and produces it back to the source topic | The original connector is fixed and can now read it |
| Second connector on the DLQ | A separate sink connector reads the DLQ topic and writes to the destination with lenient settings | You want the destination to store the raw form for later cleaning |
| Repair job, then direct write | A stream job or script repairs records and writes them straight to the destination | The destination is simple and the source topic should not be touched |
| Manual fix | An engineer corrects a small number of records by hand | Only a few records failed |
| Discard | Leave them to expire, with a recorded reason | The records are truly junk |
flowchart LR
DLQ["DLQ topic"] --> RJ["Repair job"]
RJ -->|"fixed record"| SRC["Original source topic"]
SRC --> CON["Sink connector (now fixed)"]
CON --> DEST[("Destination")]
RJ -->|"cannot repair"| PARK["Parking topic for manual review"]
The repair job reads records from the DLQ, tries to fix each one, and sends good results back to the source topic where the fixed connector picks them up. Records that cannot be repaired go to a parking topic for a person to review. This keeps the DLQ from turning into a permanent pile.
Be careful with duplicates and order
- Duplicates. If the record partly reached the destination before failing, a replay could create a duplicate. Prefer destinations and settings where writes with the same key replace the earlier version.
- Order. A replayed record arrives late. If newer data for the same key has been written, the old value could overwrite the newer one. Compare timestamps or version fields before replaying.
- Loops. If you replay a record before fixing the cause, it will fail again and return to the DLQ. Add a counter or a header to stop endless loops.
Returning a corrected parcel to the conveyor belt works only if the belt is now able to handle it. If you put it back before fixing the label reader, it simply lands in the problem tray again. Fix the machine first, then re-send the parcel.
Common Causes of Dead Letters, and How to Spot Them
Most DLQ records come from a short list of causes. Learn the list and triage becomes quick.
| Cause | What you see in the headers | Usual fix |
|---|---|---|
| Wrong converter for the topic | Value converter stage; messages about unexpected bytes or an unknown magic marker | Match the converter to the format the producers really use |
| Schema not found or mismatch | Value converter stage; errors about a missing schema ID or incompatible schema | Check the schema registry, permissions, and schema compatibility rules |
| Mixed formats in one topic | Some records pass, some fail at the converter | Separate topics by format, or fix the producer |
| Truncated or corrupted bytes | Converter stage; parsing errors | Investigate the producer or network path; discard if unrecoverable |
| Null or missing field in a transform | Transformation stage; null-pointer style messages | Adjust or guard the transform; handle tombstones and empty values |
| Destination rejects a value | Task write stage, if the connector reports it | Widen the column, clean the data, or filter before writing |
| Schema registry unavailable | Converter stage; connection errors, many at once | Restore the registry; use retries so these are not sent to the DLQ |
The special case of tombstones
A tombstone is a record with a key but an empty value. In Kafka, it means “delete this key”, especially in compacted topics. Many transformations and some connectors do not expect an empty value and fail. Records like these are perfectly valid, but they will keep appearing in your DLQ until you handle them, for instance by making the transformation skip empty values or by configuring the connector to treat them as deletes.
A blank form marked “cancel this booking” is not a broken form. It is an instruction. A clerk who only knows how to process filled forms will put it in the problem tray, even though it was valid all along.
Bulk failures versus trickles
A trickle (a few records)
- Usually isolated bad data from one producer
- The DLQ is doing its job
- Investigate at normal priority
A flood (thousands quickly)
- Usually a configuration, schema, or release problem
- Continuing means filling the DLQ with junk
- Pause the connector, fix the cause, then resume
Alert on the rate of DLQ arrivals, not only the total. A sudden jump from near zero to hundreds per minute is the sign of a flood that deserves immediate attention.
DLQs Elsewhere: How Connect Compares
The dead letter idea is older than Kafka. Comparing it helps you place Connect’s version correctly.
Classic message queues such as Amazon SQS or RabbitMQ have a dead letter feature built into the broker. After a message fails delivery several times, the broker itself moves it to a special queue. Kafka works differently. Kafka brokers do not track whether a consumer succeeded. They only store records and serve them by offset. So any dead letter behavior in Kafka must be built in the client or framework that reads the records.
| System | Who moves the bad message? | Notes |
|---|---|---|
| Queue services such as SQS | The broker or service, after a set number of failed deliveries | Built in and automatic |
| Plain Kafka consumer | Your own code | You must write the logic to catch, route, and log |
| Kafka Connect sink | Connect’s error handling layer | Built in, configured by settings, sink connectors only |
| Kafka Streams | Exception handlers in your application | Handlers decide to fail or continue; routing to a DLQ topic can be added, and newer versions offer more built-in support, so check your version |
| Framework libraries such as Spring for Apache Kafka | A recoverer component in the framework | Publishes failed records to a dead letter topic after retries |
In a traditional post office, the sorting machine has its own built-in problem tray. In Kafka, the warehouse only stores the parcels. The delivery drivers (the clients and frameworks) each bring their own problem tray. Kafka Connect is one type of driver that brings a tray by default when you ask for it.
The “retry topic” cousin
Some teams use a related design with several topics, for example a “retry in one minute” topic, a “retry in ten minutes” topic, and finally the dead letter topic. A failed record hops from one to the next, giving temporary problems time to clear without blocking the main flow. Kafka Connect does not build this ladder for you. It offers in-place retries with backoff and a single DLQ.
Real-world picture: choose the tool that fits
A team loading data into a warehouse uses Connect and its DLQ because it needs no code. Another team processes payments with custom services and builds a retry ladder with several topics, because each step has business meaning. Both use the dead letter idea, at different levels of effort and control.
Choosing Your Error Strategy: A Decision Guide
Walk through these questions when setting up a new sink connector.
Is this pipeline brand new or still being tested?
Start with fail-fast so you notice every issue. Move to a DLQ once the data has proven stable.
Is the data such that no single record may ever be lost or delayed?
Use fail-fast plus strong alerting, or a DLQ with tight alerting and rapid human response.
Can the business live with a few records arriving late?
Use a DLQ with tolerance “all”, context headers, and alerts.
Do outside services sometimes fail briefly?
Add a retry timeout with backoff so temporary trouble does not fill the DLQ.
Who will watch and handle the DLQ?
Name an owner and a process before turning it on. No owner, no DLQ.
Matching strategy to situation
| Situation | Suggested setup | Why |
|---|---|---|
| New pipeline in development | Tolerance none, logging on | Surface every problem early |
| Analytics feed into storage | Tolerance all, DLQ with headers, short retry window, alerts | Freshness matters and the odd bad record can wait |
| Payments or ledger sink | Tolerance none, or DLQ with instant paging | Skipping a record silently is unacceptable |
| Data that depends on an external schema registry | Retry timeout of a few minutes, then DLQ | Registry outages are temporary |
| Topic with mixed or uncertain formats | Tolerance all, DLQ, and a plan to fix the producers | Keeps healthy data moving while the mess is cleaned |
| Load test with junk data | Tolerance all, no DLQ, logging off | You do not care about individual records |
Advantages, Disadvantages, and Trade-offs
A DLQ buys you continuity. It costs you attention.
Advantages
- One bad record no longer halts the pipeline
- Original bytes are preserved for study and replay
- Headers explain why each record failed
- No custom code needed to start
- It is an ordinary topic, so all Kafka tools work on it
- Good records stay fresh and low in lag
Disadvantages
- Only works for sink connectors
- Records reach the destination out of order if replayed later
- Needs someone to watch and act
- Can hide a systemic problem if alerts are missing
- Adds a copy of possibly sensitive data in another topic
- Does not cover every error unless the connector reports it
Trade-offs at a glance
| Trade-off | Fail-fast | Skip with log only | DLQ |
|---|---|---|---|
| Data loss risk | None | High | Low, if watched |
| Pipeline availability | Low | High | High |
| Recovery effort | Restart and fix at once | Hard to recover records | Triage and replay at leisure |
| Operational load | Urgent pages | Hidden until data is missing | Steady monitoring |
| Best fit | Critical, low-volume feeds | Disposable data | Most production pipelines |
The deepest trade-off is latency of attention. Fail-fast makes the problem loud right now. A DLQ makes it quiet now, and important later. If your team is good at watching dashboards and responding within hours, a DLQ is a great fit. If nobody looks at anything for days, a loud failure may protect you better.
Security Considerations
A dead letter queue is a copy of your problem data. Treat it as seriously as the original.
Restrict who can read
The DLQ holds raw records. Give read access only to the small group who triage failures.
Restrict who can write
Only the Connect worker identity should write to the DLQ, so nobody can plant fake records.
Encrypt connections
Use TLS between workers and brokers, including for the DLQ producer.
Encrypt storage
Encrypt the disks that hold topic data, since the DLQ can contain personal information.
Personal data in the DLQ
If the source topic contains personal data, so does its DLQ. The DLQ can even hold more risk, because records in it often live longer while people investigate, and because headers can include text from error messages and stack traces. Apply the same retention, deletion, and privacy rules to the DLQ as to the source. If a customer asks for deletion, remember that the DLQ may hold a copy.
The setting that includes record details in worker logs can leak sensitive data into log files, which are often widely readable. Keep it off unless you have strong log protection, and prefer to look at the DLQ, which you can lock down properly.
Authorization for the Connect worker
The Connect worker identity needs permission to write to the DLQ topic, and to create it if you let Connect create topics. In tightly controlled clusters, create the DLQ topic yourself in advance, with the exact permissions you want, and give the worker only write access. This also avoids surprises in topic settings.
Cleaning up sensitive stack traces
Stack traces help developers but can expose internal class names and library versions. If DLQ topics are shared with a wider audience, such as support staff, consider giving them a cleaned view: a small job that reads the DLQ, removes stack traces, and writes a summary to another topic for wider access.
Real-world picture: a regulated industry
A bank restricts its DLQ topics to an operations group, encrypts them, sets a fixed retention, and audits every read. It treats the DLQ as regulated data, because a failed payment record is still a payment record.
Monitoring, Logging, and Metrics
The DLQ only helps if somebody notices when it fills.
Metrics that Connect gives you
Each task reports error metrics. The names differ slightly between tools, but the ideas are the same.
| Metric idea | What it tells you |
|---|---|
| Record errors | How many records hit an error at any stage |
| Record failures | How many failures happened, including repeated ones for the same record |
| Records skipped | How many records were dropped because errors are tolerated |
| Retries | How often Connect had to try again |
| Errors logged | How many errors were written to the log |
| DLQ produce requests | How many records Connect tried to write to the dead letter topic |
| DLQ produce failures | How many times writing to the dead letter topic itself failed |
| Time of last error | When the most recent error happened |
flowchart LR
W["Connect workers metrics"] --> COL["Metrics collector"]
DT["DLQ topic size and rate"] --> COL
LG["Worker logs"] --> LP["Log platform"]
COL --> DB["Dashboards"]
COL --> AL["Alert rules"]
LP --> DB
AL --> ON["On-call team"]
Worker metrics and DLQ topic statistics flow into a collector. Worker logs flow to a log platform. Dashboards show trends, and alert rules notify the on-call team when numbers cross a line.
Alerts worth setting
- Any DLQ arrivals for critical connectors. Even one record may deserve a look.
- A sudden jump in the arrival rate. This points to a flood, not a trickle.
- DLQ produce failures above zero. This means Connect could not save a bad record, and that record may be lost.
- Old records sitting in the DLQ. Alert when the oldest record is approaching the retention limit.
- Task in the FAILED state. The classic sign of fail-fast or an unhandled error.
- Growing consumer lag. Slow processing may hide behind a busy retry loop.
Compute a simple ratio: DLQ records divided by records processed. A value of 0.01 percent is a normal trickle for many pipelines. A value of 5 percent is a fire, even if the absolute number looks small.
Reading the DLQ itself
Because the DLQ is an ordinary topic, you can build a small dashboard on it: counts by connector, by stage, by exception message, and by original topic. Many teams build one small consumer that reads the DLQ, extracts these header fields, and writes counts to their metrics system.
Performance and Scalability
A well-behaved pipeline rarely feels the DLQ. A misbehaving one can feel it a lot.
The normal case: almost free
When bad records are rare, the cost of the DLQ is tiny. The DLQ producer sits idle most of the time. A few extra writes per hour do not matter to a cluster that handles thousands of records per second.
The storm case: errors at scale
The picture changes when a large share of records fail. Imagine a wrong converter setting: every record fails. The connector now does extra work for each record: it creates an error context, retries (if configured), logs (if enabled), and produces the record to the DLQ. That means the pipeline can slow down and the DLQ topic can grow as fast as the source topic.
Keeping storms small
- Alert on the rate and pause the connector quickly during a flood.
- Turn off record details in logs so a storm does not also flood log storage.
- Size the DLQ generously, with enough partitions and disk to absorb an unexpected burst.
- Avoid long retry windows for permanent errors. A retry that cannot succeed only slows the pipeline. Keep retries for external services.
- Test with bad data before launch to learn how the pipeline behaves.
A small problem tray is never noticed at a post office. But if a whole van of misaddressed mail arrives, the tray overflows, the staff spend all day on paperwork, and normal mail slows down. The answer is to spot the van early and stop unloading it.
Scaling the DLQ with the pipeline
If a connector runs many tasks, each task writes to the same DLQ topic. Choose a partition count that supports the combined worst-case rate. Because DLQ records are usually read by a low-volume triage process, you may not need many partitions for consumers, but keep enough to spread the write load.
Reliability and High Availability
The tray must not have holes. If it fails, bad records vanish.
The DLQ is a Kafka topic, so use Kafka’s protections
Replication factor
Keep several copies of every DLQ record so a broker failure does not lose them. The Connect default of three is a sensible choice.
Minimum in-sync copies
Require more than one copy to acknowledge each write, so a record is not accepted onto a single fragile broker.
Retention
Keep records long enough for people to act, including weekends and holidays.
Disk headroom
Plan disk for a burst, so a flood does not fill the cluster.
What if writing to the DLQ fails?
Writing to the DLQ is a normal Kafka produce call, and it can fail, for example when the topic does not exist and cannot be created, when permissions are missing, or when the brokers are unreachable. In such cases Connect logs the problem and counts it in the “DLQ produce failures” metric. The record that could not be saved may be lost, since it has already been skipped. This is why the produce failure metric deserves an alert of its own, and why the topic and permissions should be prepared in advance.
A DLQ configured with a topic name that the worker cannot write to gives a false sense of safety. Test the whole path once, on purpose, with a deliberately bad record, and confirm it appears in the topic with headers.
Worker and task failures
Connect itself is built for reliability. If a worker dies, its tasks move to other workers, and they resume from the last committed offsets. Records that were already sent to the DLQ stay there safely in Kafka. Records that were in flight may be processed again after the move, so a bad record might be written to the DLQ more than once. Design your triage and replay steps to tolerate duplicates.
flowchart LR
W1["Worker A runs task 1"] --> DLQ["DLQ topic replicated on three brokers"]
W2["Worker B runs task 2"] --> DLQ
W1 -. "worker fails" .-> X["Task 1 moves"]
X --> W3["Worker C resumes task 1 from committed offset"]
W3 --> DLQ
The picture shows two workers writing to a replicated DLQ topic. When worker A fails, its task moves to worker C, which resumes from the last committed offset. The DLQ keeps every record already written, and the resumed task may re-send a small number of records, which is why duplicates are possible.
Deployment and Cloud
A few simple habits make DLQs predictable across environments.
Treat error settings as code
Keep connector configurations in source control, not only in a running system. The error handling settings belong right beside the rest of the connector’s settings, with comments that explain the choices. Reviews then catch dangerous combinations, such as tolerance “all” without a DLQ.
Naming conventions
| Choice | Example | Benefit |
|---|---|---|
| One DLQ per connector | dlq-orders-storage-sink | Clear ownership and separate volumes |
| One DLQ per team | dlq-payments-team | Fewer topics but mixed sources; rely on headers to separate |
| Prefix with environment | prod-dlq-orders-sink | Avoids mixing test and production records |
Creating the topic ahead of time
Connect can create a missing DLQ topic if permissions allow, but production teams normally create it in advance. That way they choose the partition count, retention, replication, minimum in-sync copies, and permissions, instead of accepting defaults.
Managed and cloud services
Cloud teams have several ways to run Connect. Amazon MSK Connect and managed Connect offerings from Kafka vendors run standard Kafka Connect underneath, so the same error settings apply. Some managed connectors expose dead letter queue options in their own screens or configuration forms. Details differ between providers and connectors, so check the current documentation for the service you use.
Create the DLQ topic
With chosen partitions, retention, replication, and permissions.
Deploy the connector with error settings
Tolerance, DLQ name, headers, retries, and logging, all reviewed.
Send a deliberately bad record
Confirm it lands in the DLQ with headers and that the pipeline keeps flowing.
Verify alerts and dashboards
Confirm the alert fires and the on-call person knows what to do.
Document the runbook
Write down the triage and replay steps so anyone can follow them.
A fire drill checks that alarms ring, exits open, and everyone knows where to go, before a real fire. Sending a test bad record is your fire drill for the dead letter queue.
Design Patterns and Anti-Patterns
The same good ideas and the same bad ideas show up in every team.
Patterns that work
Tolerate, save, alert
Keep the pipeline moving, save every failure in a DLQ, and alert people on arrivals.
Retry only for the outside world
Use short retry windows with backoff for registries and destinations, not for permanently broken records.
DLQ per connector
Clear ownership, separate volumes, easy cleanup.
Repair and replay job
A small program that fixes known problems automatically and returns records to the pipeline.
Parking topic
Records that cannot be repaired move from the DLQ to a parking topic with a reason attached, so the DLQ stays small.
Validate at the door
Check schema and rules in producers, so fewer bad records enter Kafka in the first place.
Anti-patterns to avoid
What people do
They set tolerance to all and configure no DLQ and no logging.
Why it hurts
Bad records vanish silently. The task looks healthy while data goes missing.
Better approach
Always pair tolerance “all” with a DLQ, headers, and alerts.
What people do
They create a DLQ and never look at it.
Why it hurts
Records pile up until retention deletes them. The pipeline appears fine while data is permanently lost.
Better approach
Assign an owner, set alerts, write a runbook, and review the DLQ regularly.
What people do
They leave context headers off to save a little space.
Why it hurts
Each DLQ record becomes a mystery. Investigators must guess the reason and hunt for the original.
Better approach
Turn headers on. The space cost is small compared with the time saved.
What people do
They replay the whole DLQ into the source topic before fixing the cause.
Why it hurts
The same records fail again, refill the DLQ, and can create an endless loop.
Better approach
Fix the cause first, replay a small test batch, and only then replay the rest.
What people do
They put the DLQ topic on default settings with short retention and a single copy.
Why it hurts
The safety net is weaker than the pipeline it protects. A broker failure or a weekend can erase the evidence.
Better approach
Replicate, set a sensible minimum in-sync count, and choose retention that covers slow investigations.
What people do
They use the DLQ as a permanent data store or as a place to send normal business rejections.
Why it hurts
It grows without limit and mixes technical failures with business events, making both harder to handle.
Better approach
Use the DLQ for technical failures only. Route expected business rejections to their own topic with clear meaning.
Best Practices and Common Mistakes
Small habits that prevent most dead letter surprises.
Habits that pay off
Decide the policy per connector
Write down what a bad record is worth and choose fail-fast or DLQ on purpose.
Always enable context headers
They are the difference between a clue and a mystery.
Create the DLQ topic yourself
Choose partitions, retention, replication, and permissions before launch.
Alert on arrivals and on produce failures
Both the tray filling and the tray breaking need a notification.
Rehearse with a deliberately bad record
Prove the full path works before real data depends on it.
Write a runbook
Triage, repair, replay, and discard steps, with names of the people responsible.
Fix root causes, not only symptoms
A DLQ that fills every week is a signal that a producer or a schema needs attention.
Common mistakes
Forgetting tolerance
Setting a DLQ name without setting tolerance to all. The task still fails on the first error.
Expecting source DLQ
Trying the setting on a source connector, where it does not apply.
Assuming every error is caught
Errors inside a connector’s own write step may not reach the DLQ.
Retrying broken records for hours
Long retries on permanent errors just delay the pipeline.
Wrong converter blamed on data
Filling the DLQ with perfectly good records because the converter does not match the topic format.
Ignoring tombstones
Valid delete markers landing in the DLQ because a transform cannot handle empty values.
The first time you see a flood of DLQ records after a release, check the boring things first: the converter settings, the schema registry address, and any recent producer change. Most floods come from a configuration mismatch, not from truly corrupt data.
A Guided Walkthrough: Loading Orders Safely
Let us design the error handling for one real pipeline from start to finish.
Brightcart, an online store, publishes an orders topic. A sink connector copies orders into a cloud storage bucket every few minutes, where analysts query them. The connector uses an Avro converter with a schema registry, and one transformation that adds a “received time” field.
flowchart LR
APP["Mobile and web apps"] --> ORD["orders topic"]
ORD --> SNK["Storage sink connector: tolerance all, retry 5 minutes"]
SNK -->|"good records"| S3[("Cloud storage bucket")]
SNK -->|"bad records with headers"| DLQ["dlq-orders-storage-sink"]
DLQ --> AL["Alert on arrivals"]
DLQ --> RJ["Repair job"]
RJ -->|"fixed"| ORD
RJ -->|"unfixable"| PK["parking-orders topic"]
Decide the policy
Analytics values freshness, and a few late records are fine. So the team chooses tolerance “all” with a DLQ.
Prepare the topic
They create dlq-orders-storage-sink with three copies, a minimum of two in-sync copies, and two weeks of retention. Only the Connect identity can write to it.
Set retries
The schema registry sometimes restarts during upgrades, so they allow five minutes of retries with backoff before a record is treated as bad.
Turn on headers and logging
Headers are enabled. Error logging is enabled, but record details are left out of the logs.
Rehearse
They publish one deliberately malformed order to a test topic and confirm it appears in the DLQ with all headers while good orders keep landing in storage.
Add alerts
Any DLQ arrival sends a message to the data team. A rate above ten per minute pages the on-call engineer. Any DLQ produce failure pages immediately.
A week in production
On Monday, a new mobile release accidentally sends order values as text instead of numbers for a small share of users. Fifty orders reach the DLQ. The alert fires, and an engineer groups the DLQ records by exception message. One message dominates: a type mismatch. The original topic and partition point to a single app version.
On Tuesday, the mobile team ships a fix. The data team writes a repair job that converts the text values to numbers and sends the fixed orders back to the orders topic. They replay ten records first, check the results in storage, and then replay the rest. Analysts see complete data, and nobody had to stop the pipeline.
On Thursday, the registry is restarted for an upgrade. For four minutes, the converter cannot fetch schemas. Because retries are set for five minutes, nothing reaches the DLQ. Records are simply delayed, then processed.
| Event | What the design did |
|---|---|
| Malformed orders from a new app version | Diverted to the DLQ; the pipeline kept running; headers pointed to the cause |
| Schema registry restart | Retries absorbed the outage; no false DLQ records |
| Wrong converter setting after a change | Rate alert fired within minutes; the connector was paused and fixed |
| DLQ topic permission removed by mistake | DLQ produce failure alert fired; permission restored before more records were lost |
| Truly corrupted record | Moved to the parking topic with a reason; expired later |
Myths and Misconceptions
Wrong ideas about dead letter queues travel from team to team. Here are the corrections.
“Kafka has a built-in dead letter queue.”
Kafka brokers do not. Frameworks and tools such as Connect add the behavior on the client side.
“Naming a DLQ topic is enough.”
You must also set tolerance to all, otherwise the task still fails on the first error.
“The DLQ catches every possible error.”
It covers converters and transformations, and connector errors only when the connector reports them.
“A DLQ means we never lose data.”
Not if nobody watches it, if retention is too short, or if writing to the DLQ itself fails.
“Source connectors can use the same setting.”
The DLQ setting applies to sink connectors only.
“Replaying is automatic.”
Connect never replays for you. You design the repair and replay step.
Think of a DLQ as a smoke detector, not a fire extinguisher. It tells you something is wrong and keeps the evidence. People still have to come and deal with the fire.
Real-World and Industry Patterns
These situations appear again and again wherever teams move data with Kafka Connect.
Loading a data lake
Companies copy event topics into cloud storage for analysis. Producers come from many teams, so occasional malformed events are certain. Teams run sink connectors with a DLQ so the lake stays fresh, and they review the DLQ daily to find which producer needs help.
Feeding a search engine
Sink connectors that write documents into a search engine meet rejected documents, such as fields with the wrong type. A DLQ keeps the search index updating and preserves the rejected documents for repair.
Writing to relational databases
Sink connectors that insert into database tables run into length limits and constraint violations. Teams use connectors that report individual rejected rows, so only the offending rows land in the DLQ, and the rest of the batch is written.
Regulated industries
Financial and healthcare teams often prefer fail-fast, or a DLQ with paging on every arrival, because a skipped record can be a compliance problem. They also encrypt and audit the DLQ like any sensitive topic.
Migrations and schema changes
During schema changes, some records use the old shape and some the new. A DLQ acts as a safety valve while producers and consumers catch up, and the arrival rate becomes a useful measure of migration progress.
Operating the Pipeline: Pause, Restart, and Inspect
Knowing the everyday controls turns a stressful incident into a routine job.
Kafka Connect in distributed mode comes with a small web interface, often called the REST interface, for managing connectors. You use it to look at what is happening and to change it. During a dead letter incident, you will use it in four situations.
Check status
See whether a connector and each of its tasks are running, paused, or failed. A failed task shows the error trace that stopped it.
Pause
Pause a connector during a flood, so it stops consuming and stops filling the DLQ, while you investigate. Records wait safely in the source topic.
Resume
Resume after the cause is fixed. The connector continues from its saved position.
Restart
Restart a failed task, or a whole connector. Newer versions let you restart only the failed parts, which is gentler on healthy tasks.
The REST interface is the control panel at the side of the conveyor belt. It has a status display, a pause switch, a resume switch, and a reset button for jammed stations. A calm operator uses them in order: look, pause, fix, resume.
Changing settings safely
When you change a connector’s configuration, such as adding a DLQ or fixing a converter, Connect stores the new configuration and restarts the connector’s tasks so that they pick it up. During that moment, tasks stop and restart, which is a small pause in processing. Plan changes for a quiet time when you can, and check the status afterwards to confirm that every task returned to the running state.
Where the connector’s position is stored
A sink connector’s position in its source topic is stored in the normal Kafka consumer offsets for a group that Connect creates for that connector. This has a useful consequence. When you fix a problem and resume, the connector continues where it left off, and no records are skipped or repeated, apart from the small number that may be repeated after an unclean stop. Newer versions of Kafka Connect also allow a stopped connector’s offsets to be viewed and changed through the same interface, which can help in rare cases where you need to move the connector’s position deliberately. Use this carefully, because moving the position forward skips records and moving it backward repeats them.
Look at the status
Is the connector running, and are all tasks running? Note the times and any error trace.
Pause if the DLQ is flooding
Stopping the flow protects the DLQ from filling with junk and gives you time.
Find the cause
Read the DLQ headers, the worker log, and any recent changes to producers or settings.
Apply the fix
Correct the configuration, the schema, or the producer. Test with a small sample if you can.
Resume or restart
Bring the connector back and watch the DLQ arrival rate fall to normal.
Replay the saved records
Repair and replay the DLQ records that were caught during the incident.
Write this six-step routine into your team’s runbook, with the exact commands or screens your team uses. When an alert fires at night, a short checklist beats a clever memory every time.
Reading the worker log
The worker log shows what the connector was doing when an error happened. With error logging enabled, each failure produces a line naming the stage, the exception, and the location of the record. Combined with the DLQ headers, it lets you rebuild the story: which record, which step, which reason, and at what time. Keep logs long enough to cover your investigation window, and remember that log lines with record details can hold sensitive data.
Frequently Asked Questions
Summary and Key Takeaways
A dead letter queue in Kafka Connect is a practical answer to a simple problem: one bad record should not stop a whole pipeline. By setting aside records that a sink connector cannot process, Connect keeps healthy data flowing and keeps the failed records safe for study and repair. The idea is easy, but it works well only when the surrounding habits are in place: tolerance, headers, retries, alerts, owners, and a plan for replay.
Key Takeaways
- What it is: an ordinary Kafka topic that receives copies of records a sink connector could not process.
- How to enable it: set tolerance to all, name a DLQ topic, and turn on context headers so every record explains itself.
- Sink only: source connectors have no built-in DLQ, so use a two-stage design or connector-specific options.
- Know what is covered: converters and transformations are covered automatically, and connector write errors only when the connector reports them.
- Retry for the outside world: use short retries with backoff for registries and destinations, not for permanently broken records.
- Never skip silently: tolerance “all” without a DLQ or alerts is silent data loss.
- Own the tray: create the topic yourself, replicate it, secure it, set retention that covers slow investigations, alert on arrivals and on produce failures, and write a runbook for triage, repair, and replay.