Kafka
Apache Kafka is the event streaming platform behind LinkedIn, Uber, Netflix, and most Java microservices architectures.
Introduction
Apache Kafka is the event streaming platform behind LinkedIn, Uber, Netflix, and most Java microservices architectures. Producers publish records to topics partitioned for parallelism; consumers in consumer groups read with at-least-once or exactly-once semantics.
Java engineers interact with Kafka via spring-kafka, the official kafka-clients library, or Kafka Streams. This lesson covers production fundamentals: partition strategy, consumer group rebalancing, offset management, and exactly-once processing with idempotent producers and transactional APIs.
Business problem
Why synchronous microservices fail at scale:
- Cascade failures: Order service synchronously calls inventory, payment, notification — one slow service blocks all.
- Peak load: Black Friday order spike — HTTP thread pools exhaust; events buffer in Kafka instead.
- Audit and replay: Regulators require event log — REST calls leave no durable history.
- Multiple consumers: Same OrderCreated event feeds inventory, analytics, fraud — REST requires fan-out from producer.
- Decoupled deploy: New fraud service subscribes to existing topic — zero changes to order producer.
Why this topic exists
Kafka solves the "n×m integration problem" — n producers × m consumers without point-to-point wiring:
- Log abstraction: Durable append-only log — producers write; consumers read at their pace with offset bookmarks.
- Partitions: Parallelism — topic split into ordered partitions; scale consumers up to partition count.
- Consumer groups: Each partition consumed by one consumer in group — horizontal scale with automatic rebalancing.
- Retention: Events replayable for days — new consumer rebuilds state from history.
- Exactly-once: Idempotent producer + transactions — payment events processed once despite retries.
Core concepts
Kafka core concepts for Java developers:
- Producer: Sends records to topic partition — key determines partition (hash(key) % numPartitions).
- Consumer: Polls batches from assigned partitions; commits offset after processing.
- Partition: Ordered sequence within partition — no global order across partitions.
- Consumer Group: Group ID — Kafka assigns each partition to one consumer; add consumer → rebalance.
- Offset: Position in partition log — auto-commit vs manual commit for at-least-once control.
- Exactly-once: enable.idempotence=true + transactional producer + read_committed consumer isolation.
Internal architecture
Kafka cluster topology for Java services:
Topic: order.events (6 partitions, replication-factor 3)Producer (Order Service) Consumer Group: inventory-workers│ key=orderId ├─ Consumer 1 → P0, P1▼ ├─ Consumer 2 → P2, P3┌─────────┐ ┌─────────┐ └─ Consumer 3 → P4, P5│ Broker 1│ │ Broker 2│ ┌─────────┐│ P0 L │ │ P1 L │ │ Broker 3│ Consumer Group: analytics│ P1 F │ │ P2 L │ │ P0 F │ └─ Consumer 1 → all partitions└─────────┘ │ P3 L │ │ P2 F │ (separate group = independent read)└─────────┘ └─────────┘Exactly-once flow:producer.initTransactions()producer.beginTransaction()producer.send(orderEvent)producer.sendOffsetsToTransaction(offsets, consumerGroupMetadata)producer.commitTransaction()
Producer → partition → consumer group flow:
Code walkthrough
Spring Kafka producer and consumer — order events with manual ack:
- ENABLE_IDEMPOTENCE: Broker deduplicates retries via producer ID and sequence numbers.
- Key = orderId: All events for one order land in same partition — ordered processing.
- Manual ack: Offset committed only after DB write succeeds — at-least-once without data loss on crash.
- groupId: Multiple inventory service instances share load — one partition per consumer max.
@Configurationpublic class KafkaConfig {@Beanpublic ProducerFactory<String, OrderEvent> producerFactory() {Map<String, Object> props = new HashMap<>();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka:9092");props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // exactly-once producerprops.put(ProducerConfig.ACKS_CONFIG, "all");return new DefaultKafkaProducerFactory<>(props);}}@Servicepublic class OrderEventPublisher {private final KafkaTemplate<String, OrderEvent> kafka;public void publish(OrderEvent event) {// key = orderId ensures all events for order go to same partitionkafka.send("order.events", event.orderId(), event);}}@Componentpublic class InventoryConsumer {@KafkaListener(topics = "order.events", groupId = "inventory-workers",containerFactory = "manualAckFactory")public void onOrderCreated(OrderEvent event, Acknowledgment ack) {reserveStock(event.orderId(), event.items());ack.acknowledge(); // commit offset after successful processing}}
Production example
Exactly-once consume-process-produce with KafkaTransactions:
- transaction-id-prefix: Enables transactional producer — begin/commit around listener.
- read_committed: Consumers skip messages from aborted transactions.
- EOS listener: Spring Kafka ties consumer offset to outgoing publish in one transaction.
- Trade-off: Higher latency than at-least-once — use for payment/fraud, not analytics.
@Beanpublic ConcurrentKafkaListenerContainerFactory<String, OrderEvent> eosFactory(ConsumerFactory<String, OrderEvent> cf,KafkaTemplate<String, ProcessedEvent> template) {var factory = new ConcurrentKafkaListenerContainerFactory<String, OrderEvent>();factory.setConsumerFactory(cf);factory.getContainerProperties().setAckMode(AckMode.RECORD);factory.setKafkaTemplate(template); // enables EOS listenerreturn factory;}@KafkaListener(topics = "order.events", groupId = "fraud-check",containerFactory = "eosFactory")public void process(OrderEvent event) {FraudScore score = fraudEngine.score(event);// Transactional: offset commit + output publish atomickafkaTemplate.send("fraud.results", event.orderId(), new FraudResult(score));}// application.ymlspring.kafka:producer:transaction-id-prefix: fraud-tx-consumer:isolation-level: read_committed // skip aborted transactions
Enterprise case study
LinkedIn — Kafka origin: LinkedIn built Kafka to handle real-time activity feeds — billions of events/day. Java services produce page views, job applications, messaging events. Key lesson: partition count is a capacity planning decision — increasing partitions later is painful; plan for peak consumer throughput upfront.
- Before: Point-to-point queues — adding consumer required producer changes.
- After: Kafka log — unlimited consumers at their own pace; 7-day retention for replay.
- Java stack: Kafka Streams for aggregations; spring-kafka for service integration.
- Ops: Monitor consumer lag — alert when lag > 10K messages for payment topics.
Performance considerations
Kafka performance tuning for Java:
- Batching: linger.ms=5, batch.size=32KB — amortize network round-trips.
- Compression: lz4 or zstd on producer — CPU for bandwidth trade-off worth it.
- Partition count: Start with #partitions ≥ peak consumer instances; order key avoids hot partitions.
- Consumer poll: max.poll.records and processing time < max.poll.interval.ms — avoid rebalance storm.
- Serializers: Avro/Protobuf + Schema Registry — smaller payloads than JSON at scale.
Security considerations
Kafka security for Java clients:
- SASL/SCRAM or mTLS: Authenticate producers/consumers — never plaintext in production.
- ACLs: Order service WRITE on order.events only — principle of least privilege.
- No PII in keys: Keys logged in metrics — use opaque order ID not email.
- Encryption at rest: Broker disk encryption + TLS in transit.
Scalability considerations
Scaling Kafka consumers and producers:
- Horizontal consumers: Add pods until consumer count = partition count — then add partitions (requires planning).
- Multiple consumer groups: Inventory and analytics both read order.events — independent scale.
- Broker scale: Add brokers + rebalance partitions — use cruise control for automation.
- Compacted topics: changelog topics for Kafka Streams state — retention by key, not time.
Production challenges
Real Kafka production issues:
- Consumer lag: Slow handler or insufficient consumers — scale or optimize processing.
- Rebalance storm: Processing exceeds max.poll.interval.ms — consumer kicked, partitions reshuffled, duplicate processing.
- Hot partitions: Null key or skewed key distribution — one partition overloaded.
- Poison messages: Deserialization failure — DLQ pattern with @RetryableTopic (Spring Kafka 2.7+).
- Schema evolution: Avro backward compatibility — new fields optional, never remove required fields.
Common mistakes
- Auto-commit before processing completes — message lost on crash after commit.
- More consumers than partitions — idle consumers waste resources; no speedup.
- No message key — round-robin partitioning loses per-entity ordering.
- Ignoring consumer lag alerts — backlog grows until retention deletes unprocessed messages.
- Exactly-once everywhere — unnecessary latency; at-least-once + idempotent handler often sufficient.
Debugging guide
Debug Kafka issues in Java services:
- Consumer lag:
kafka-consumer-groups.sh --describe --group inventory-workers. - Log correlation: Enable
logging.level.org.apache.kafka=INFO— watch rebalance events. - Trace propagation: Inject traceparent in record headers — link producer span to consumer span.
- Stuck consumer: Check max.poll.interval.ms vs handler duration — thread dump on consumer pod.
# Consumer group lagkafka-consumer-groups.sh --bootstrap-server kafka:9092 \--describe --group inventory-workers# Inspect topic messageskafka-console-consumer.sh --bootstrap-server kafka:9092 \--topic order.events --from-beginning --max-messages 5# List partitions and leaderskafka-topics.sh --describe --topic order.events --bootstrap-server kafka:9092
Best practices
- Use message keys for entities needing ordered processing (orderId, userId).
- Set acks=all and min.insync.replicas=2 for durability.
- Enable idempotent producer — free dedup on retries.
- Manual commit or transactional listener after side effects succeed.
- Monitor consumer lag per group — primary health metric.
- Use Schema Registry (Avro) for production topics — not raw JSON.
- Implement DLQ for poison messages — don't block partition indefinitely.
Anti-patterns
- Using Kafka as job queue without keys — ordering and fairness break down.
- Giant messages (>1MB) — use S3 reference + event with pointer; Kafka for metadata.
- Single partition topic for high throughput — bottleneck; no consumer parallelism.
- Sync blocking send in request thread — use async callback or separate executor.
- Consumer doing heavy work in poll thread — blocks heartbeat; use async processing with pause/resume.
Staff engineer notes
- Staff engineers default to at-least-once + idempotent consumer — exactly-once only when financial correctness demands it.
- Partition count is forever-ish — changing it reorders keys; plan 2–3× expected peak consumer count.
- Consumer group ID is a deployment identity — never reuse group ID for different logic (reads wrong offsets).
- Kafka is not a database — retention expires data; use compacted topics or external store for source of truth.
- Spring @KafkaListener is production-ready — but understand what it wraps: poll loop, ack, rebalance listener.
Interview questions
Interview preparation
15 questions grouped by difficulty — expand any card for a model answer and follow-up probe.
Beginner
1What is Apache Kafka?
BeginnerModel answer
Distributed event streaming platform.
Producers append records to topics split into partitions.
Consumers read via pull model with offset tracking.
Durable, replayable, high-throughput log abstraction.
Follow-up probe
vs RabbitMQ?
2What is a Kafka partition?
BeginnerModel answer
- Ordered, immutable sequence of records within a topic. Parallelism unit
- each partition hosted on one broker leader. Records within partition strictly ordered; no order guarantee across partitions.
Follow-up probe
How choose partition count?
3What is a consumer group?
BeginnerModel answer
id.
Kafka assigns each partition to exactly one consumer in group.
Scale consumers up to partition count.
Rebalance on consumer join/leave.
Follow-up probe
Two groups same topic?
4How does a Kafka producer work?
BeginnerModel answer
Serializes key/value, determines partition (key hash or round-robin), sends batch to broker leader.
Waits for ack per acks config (0, 1, all).
Retries on transient failure; idempotent producer deduplicates.
Follow-up probe
What is acks=all?
5What is a consumer offset?
BeginnerModel answer
Pointer to last consumed position in partition.
Stored in __consumer_offsets topic.
Auto-commit periodically or manual commit after processing.
Enables resume after restart.
Follow-up probe
Commit before or after process?
Intermediate
6Explain at-least-once vs exactly-once.
IntermediateModel answer
- At-least-once: may redeliver on failure
- handler must be idempotent. Exactly-once: idempotent producer + transactional API + read_committed
- broker guarantees no duplicates within transaction scope. EOS in Kafka is end-to-end with external systems only with transactional outbox pattern.
Follow-up probe
Is EOS truly exactly-once?
7What triggers consumer rebalance?
IntermediateModel answer
- Consumer joins/leaves group, session timeout (missed heartbeat), max.poll.interval exceeded (processing too slow), partition count change. During rebalance consumption pauses
- minimize with cooperative sticky assignor.
Follow-up probe
Avoid rebalance storm?
8Why use message keys?
IntermediateModel answer
- Same key always goes to same partition
- preserves ordering per entity (all order-123 events ordered). Without key, round-robin
- no per-entity order. Key also affects load distribution.
Follow-up probe
Hot key problem?
9How implement idempotent consumer in Java?
IntermediateModel answer
- Store processed message ID (event ID or offset+partition) in DB before ack. On redelivery, check if already processed
- skip. Or use natural idempotency (UPSERT by business key). At-least-once + idempotent handler = effective exactly-once.
Follow-up probe
Where store dedup state?
10Spring Kafka @KafkaListener internals?
IntermediateModel answer
Wraps KafkaConsumer poll loop in thread.
Deserializes record, invokes method, commits offset per AckMode.
Concurrent listeners = one thread per partition max typically.
Error handlers route to retry/DLQ.
Follow-up probe
AckMode values?
Advanced
11Design order pipeline with Kafka.
AdvancedModel answer
events (12 partitions, key=orderId).
Order service produces OrderCreated.
Inventory group consumes, reserves stock, produces InventoryReserved or Failed.
Payment consumes, charges, produces PaymentCompleted.
Saga via events.
DLQ for poison.
Lag alerts.
Avro schemas.
Follow-up probe
Orchestration vs choreography?
12When NOT to use Kafka?
AdvancedModel answer
Low volume request-reply, task needing immediate single response, small team without ops capacity.
Use REST/RabbitMQ for simple queues.
Kafka shines at high throughput, replay, multiple consumers, event sourcing.
Follow-up probe
Kafka vs event bus?
13How handle schema evolution?
AdvancedModel answer
Avro/Protobuf with Schema Registry.
Backward compatible changes: add optional fields with defaults.
Never delete required fields.
Consumers with old schema read new data.
CI validates compatibility.
Follow-up probe
Breaking change strategy?
14Explain Kafka transactions.
AdvancedModel answer
id.
beginTransaction → send records → sendOffsetsToTransaction (consumer offsets) → commitTransaction.
Atomic: all sends visible or none.
Consumer isolation read_committed skips aborted.
Follow-up probe
Performance cost?
15Partition reassignment impact?
AdvancedModel answer
- Adding partitions changes key→partition mapping for new keys only (sticky partitioner helps). Increasing partitions does not split existing data
- old records stay on old partitions. Consumers rebalance. Plan partition count upfront; use cruise control for broker balance.
Follow-up probe
Can decrease partitions?
Hands-on exercise
Lab: Kafka concepts in Java:
- Run playground — observe partition assignment simulation output.
- Add PaymentFailed event to sealed hierarchy — update handler switch.
- Write pseudo @KafkaListener method with manual ack comment.
- Calculate: 6 partitions, 4 consumers — how many idle consumers?
- Bonus: explain when you'd choose exactly-once vs at-least-once for payment events.
JavaApache Kafka for Java
Press Run to execute. Real javac + JVM via remote API. Falls back to simulator on network failure.Architecture trade-offs
- At-least-once vs exactly-once: At-least-once wins simplicity; exactly-once wins financial correctness at latency cost.
- Kafka vs RabbitMQ: Kafka wins throughput and replay; RabbitMQ wins routing flexibility and low-latency task queues.
- JSON vs Avro: JSON wins dev speed; Avro wins payload size and schema governance.
- Choreography vs orchestration: Kafka events enable choreography; orchestrator adds visibility at coupling cost.
Summary
Kafka is the nervous system of Java microservices — master producers, consumers, partitions, consumer groups, and exactly-once before wiring Spring @KafkaListener in production. Plan partition count early, key your messages, and treat consumer lag as a page-worthy alert.
Key takeaways
- Topics + partitions = parallelism; keys preserve per-entity ordering.
- Consumer groups scale consumption — max one consumer per partition per group.
- Idempotent producer + manual ack = production baseline.
- Exactly-once for money paths; at-least-once + idempotent handler elsewhere.
- Monitor consumer lag — primary Kafka health metric.