Spring Integration — Cheat Sheet
Apache Kafka · 3 topics. Download the PDF or the Instagram carousel and share it.
Cheat Sheet · AiCanCode.org
Spring Integration
Apache Kafka3 topicsQuick revision reference
1
Kafka with Spring Boot
spring-kafka auto-configures KafkaTemplate for production and a ConcurrentKafkaListenerContainerFactory for consumption; set bootstrap-servers in application.properties.
- ✓spring-kafka auto-configures KafkaTemplate and a listener container factory from application.properties.
- ✓Use ErrorHandlingDeserializer to prevent a malformed message from crashing the entire consumer thread.
- ✓Set listener concurrency to match the number of partitions divided by expected instance count.
- ✓MANUAL_IMMEDIATE ack mode commits the offset only after your handler confirms success — essential for at-least-once delivery.
- ✓DefaultErrorHandler replaces the old SeekToCurrentErrorHandler (spring-kafka 2.8+); use DeadLetterPublishingRecoverer for DLT routing.
- ✓Enable spring.kafka.producer.properties.enable.idempotence=true to prevent duplicate publishes on producer retries.
Properties + Java — Spring Boot Kafka producer
# application.properties — producer
spring.kafka.bootstrap-servers=kafka-1:9092,kafka-2:9092,kafka-3:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
# Reliability settings
spring.kafka.producer.acks=all # wait for all ISR replicas
spring.kafka.producer.retries=3
spring.kafka.producer.properties.enable.idempotence=true
# Throughput
spring.kafka.producer.batch-size=65536 # 64 KB batch
spring.kafka.producer.properties.linger.ms=5
# Sending via auto-configured KafkaTemplate
@Service
public class OrderProducer {
@Autowired KafkaTemplate<String, OrderEvent> template;
public void publish(OrderEvent event) {
CompletableFuture<SendResult<String, OrderEvent>> future =
template.send("order-events", event.getOrderId(), event);
future.whenComplete((result, ex) -> {
if (ex != null) {
log.error("Send failed", ex);
} else {
log.info("Sent to partition {} offset {}",
result.getRecordMetadata().partition(),
result.getRecordMetadata().offset());
}
});
}
}2
@KafkaListener
@KafkaListener annotates a method to consume records from one or more topics; supports batch consumption, error handlers, and retry with @RetryableTopic for DLT routing.
- ✓@KafkaListener manages the consumer lifecycle, poll loop, and deserialization — you write only business logic.
- ✓concurrency = "N" starts N consumer threads per listener; effective up to the partition count of the topic.
- ✓Batch mode (type: batch) delivers a List<ConsumerRecord<K,V>> per call — much more efficient for bulk DB writes.
- ✓@RetryableTopic creates retry topics with exponential back-off and a DLT automatically — the modern replacement for manual retry loops.
- ✓Use @DltHandler in the same class to handle records that exhausted all retries (alert, persist, replay later).
- ✓Inject Acknowledgment + Channel for manual ack mode; inject @Header(KafkaHeaders.RECEIVED_PARTITION) / OFFSET for record metadata.
Java — @KafkaListener Variants
// 1. Single-record listener — one ConsumerRecord per call
@Component
public class OrderConsumer {
// topics: list of topic names
// groupId: overrides spring.kafka.consumer.group-id for this listener
// concurrency: starts N concurrent consumer threads (up to partition count)
@KafkaListener(
topics = {"orders", "order-corrections"},
groupId = "order-processor",
concurrency = "3"
)
public void onOrder(
ConsumerRecord<String, Order> record, // full record with headers
@Header(KafkaHeaders.RECEIVED_PARTITION) int partition,
@Header(KafkaHeaders.OFFSET) long offset) {
log.info("Processing order {} from partition {} offset {}",
record.key(), partition, offset);
orderService.process(record.value());
}
// 2. Simplified — just the value (Spring resolves via type)
@KafkaListener(topics = "payments", groupId = "payment-service")
public void onPayment(Payment payment) {
paymentService.handle(payment);
}
// 3. Regex subscription — all topics matching the pattern
@KafkaListener(topicPattern = "order.*", groupId = "analytics")
public void onAnyOrderEvent(ConsumerRecord<String, String> record) {
analyticsService.ingest(record);
}
}3
Dead Letter Topic (DLT)
Failed records after exhausting retries are forwarded to a DLT (topic-name.DLT); a separate listener monitors the DLT for alerting, inspection, or replaying.
- ✓A DLT receives records that could not be processed after all retries are exhausted — application-managed in Kafka (unlike broker-managed in RabbitMQ).
- ✓@RetryableTopic creates retry topics and a DLT automatically; @DltHandler handles DLT records in the same class.
- ✓DeadLetterPublishingRecoverer with DefaultErrorHandler provides manual DLT routing with exception-specific configuration.
- ✓addNotRetryableExceptions() routes records immediately to the DLT for permanent errors (schema validation, invalid data) without wasting retries.
- ✓Monitor DLT depth as an SLA metric — alert on any DLT arrival; a growing DLT indicates a systemic processing failure.
- ✓Replay DLT records after fixing the root cause by re-publishing to the original topic — keep the DLT consumer idempotent.
Java — @RetryableTopic + @DltHandler
@Component
@RequiredArgsConstructor
public class PaymentConsumer {
private final PaymentService paymentService;
private final AlertService alertService;
// Spring Kafka creates automatically:
// payments-retry-0 (1s delay)
// payments-retry-1 (2s delay)
// payments-retry-2 (4s delay)
// payments.DLT (after 3 retries exhausted)
@RetryableTopic(
attempts = "4", // 1 original + 3 retries
backoff = @Backoff(delay = 1000, multiplier = 2.0),
dltTopicSuffix = ".DLT",
autoCreateTopics = "true",
kafkaTemplate = "kafkaTemplate"
)
@KafkaListener(topics = "payments", groupId = "payment-service")
public void onPayment(ConsumerRecord<String, Payment> record) {
paymentService.process(record.value());
// Throws RuntimeException → forwarded to retry topic
// After 3 retries → forwarded to payments.DLT
}
// DLT handler — called when record lands in payments.DLT
@DltHandler
public void handleDlt(
ConsumerRecord<String, Payment> record,
@Header(KafkaHeaders.RECEIVED_TOPIC) String topic,
@Header(KafkaHeaders.EXCEPTION_MESSAGE) String errorMsg) {
log.error("Payment permanently failed after retries. topic={} key={} error={}",
topic, record.key(), errorMsg);
// Options:
// 1. Alert the on-call team
alertService.critical("Payment DLT: " + record.key(), errorMsg);
// 2. Persist to a failed_payments table for manual inspection
// 3. Emit a compensating event
}
}Learn this free with Aria, your AI tutor → AiCanCode.org/learn/kafka