kafka-error-handling — retries, DLT and poison pills
Companion project for
Kafka Error Handling with Spring Kafka 4.1: DLT, Retry Topics and Poison Pills
on ankurm.com.
Six tests against a real Kafka broker started in-process in KRaft mode. No Docker, no local
install. ./scripts/run-all.sh regenerates everything under docs/output/.
Versions
|
Version |
| JDK |
25 (Temurin 25.0.4.1+1) |
| Spring Boot |
4.1.1 |
| Spring Kafka |
4.1.1 |
| kafka-clients |
4.2.1 (Boot-managed) |
Profiles
| Profile |
What it wires |
dlt |
DefaultErrorHandler + DeadLetterPublishingRecoverer, FixedBackOff(1000, 2), PermanentFailure classified non-retryable |
dltbytes |
the same, with a byte[]-aware template map so poison pills keep their original bytes |
retrytopic |
@RetryableTopic non-blocking retries with a @DltHandler |
defaults |
the stock DefaultErrorHandler, for reading its behaviour |
Documentation
- Two kinds of failure, and why they need different machinery
- What the default actually does
- Poison pills
- The dead-letter topic
- Non-blocking retries with
@RetryableTopic
Captured output
Six things this module exists to prove
- The default is ten deliveries, zero milliseconds apart, then the record is dropped. Not
"retry with backoff", and not "dead-letter".
- The DLT suffix is
-dlt, not .DLT. Get it wrong and the recoverer logs a WARN and the
record is lost — your safety net silently drops it.
kafka_dlt-exception-fqcn is always ListenerExecutionFailedException for listener
failures. The useful header is -exception-cause-fqcn.
- A poison pill reaches the DLT base64-encoded, because the recoverer reuses the JSON
producer. A per-type template map fixes it; the module shows both transcripts.
@RetryableTopic names retry topics by delay — -retry-500, -retry-1000 — so changing
the multiplier renames them.
@Backoff from spring-retry no longer exists here. Spring Kafka 4 ships its own
@BackOff, and the attribute is backOff.