Apache Kafka — a distributed event streaming system. Spring Boot connects via spring-kafka.
Simple analogy: Kafka is like a post office with mailboxes (topics). You put a letter (message) in the "Orders" mailbox, and everyone who needs it (consumers) can pick up a copy. Letters are not lost — Kafka stores them on disk for a configurable retention period (days, weeks, or forever). Unlike a real post office, Kafka can handle millions of letters per second and multiple recipients can each read the same letter independently.
Why it matters in production: Kafka decouples services — the order service does not need to know about the notification service or the analytics service. It publishes an "OrderCreated" event, and any number of downstream services consume it independently. This enables event sourcing, audit trails, and replay of historical data — capabilities impossible with synchronous REST calls.
Configuration:
1spring:2 kafka:3 bootstrap-servers: localhost:90924 consumer:5 group-id: my-group6 auto-offset-reset: earliest7 max-poll-records: 5008 enable-auto-commit: false9 producer:10 key-serializer: org.apache.kafka.common.serialization.StringSerializer11 value-serializer: org.springframework.kafka.support.serializer.JsonSerializer12 acks: all13 retries: 314 properties:15 max.block.ms: 500016 linger.ms: 2017 batch.size: 16384
Producer (sending):
1@Service2@Slf4j3public class OrderEventPublisher {4 @Autowired private KafkaTemplate<String, OrderEvent> kafkaTemplate;56 public void publishOrderCreated(OrderEvent event) {7 kafkaTemplate.send("orders", event.getOrderId(), event)8 .addCallback(9 result -> log.info("Sent offset: {}", result.getRecordMetadata().offset()),10 ex -> log.error("Failed to send", ex)11 );12 }1314 @Transactional15 public void publishInTransaction(OrderEvent event) {16 // Kafka send participates in the DB transaction17 kafkaTemplate.executeInTransaction(kafka ->18 kafka.send("orders", event.getOrderId(), event)19 );20 }21}
Consumer (receiving):
1@Service2@Slf4j3public class OrderEventHandler {4 @KafkaListener(topics = "orders", groupId = "order-processor")5 public void handleOrderCreated(@Payload OrderEvent event,6 Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_PARTITION) int partition) {7 log.info("Received order on partition {}: {}", partition, event.getOrderId());8 try {9 processOrder(event);10 ack.acknowledge(); // Manual acknowledgment11 } catch (Exception e) {12 log.error("Processing failed, will retry", e);13 // Nack to trigger retry / DLQ14 }15 }1617 @RetryableTopic(attempts = "3", backoff = @Backoff(delay = 1000))18 @KafkaListener(topics = "orders.DLT") // Dead Letter Topic19 public void handleFailed(OrderEvent event) {20 log.error("Permanent failure for: {}", event.getOrderId());21 alertService.notify(event);22 }23}
Production best practices:
NewTopic bean: more partitions = more parallelism, but also more overhead.kafka/consumer-group metrics to track lag per consumer group.Kafka vs RabbitMQ: