The Code Codex
Theme
← Guides

Kafka

A distributed event streaming platform: the log, topics and partitions, producers and consumers, replication, delivery guarantees, queues with share groups, the ecosystem and production patterns.

31 Jan 2025· 10 min· 15 sections

In this guide · 15 sections

Why Kafka

When every system talks directly to the others, every new integration is one more wire, and the number of wires grows fast. Kafka puts a central event log in the middle: producers write once, and whoever needs it consumes when and as many times as they want.

With a central log

Orders

Kafka

Payments

Billing

Inventory

Analytics

Point-to-point integration

Orders

Billing

Inventory

Analytics

Payments

With a central log

Orders

Kafka

Payments

Billing

Inventory

Analytics

Point-to-point integration

Orders

Billing

Inventory

Analytics

Payments

“A high-performance distributed event streaming platform”:

  • High throughput and low latency: millions of messages per second in a cluster.
  • Horizontally scalable: more brokers, more partitions.
  • Storage: messages don’t disappear when read; they stay for the configured time.
  • High availability, through replication across machines.
  • Connectivity: ready-made connectors and libraries for almost everything.

The log: the core idea

Each partition of a topic is a log: a sequence of messages that only grows at the end. Each message gets a sequential number, the offset. Consumers don’t delete anything; each one only keeps track of how far it has read.

0

1

2

3

4

5

6

↑ Analytics

read up to 2

↑ Billing

read up to 5

↑ Producer

writes here

0

1

2

3

4

5

6

↑ Analytics

read up to 2

↑ Billing

read up to 5

↑ Producer

writes here

That is why two consumers can be at different points of the same log, a new consumer can read everything from the beginning and, after fixing a bug, you can rewind the offset and reprocess.

Lab: local Kafka in two minutes

With Docker, a real broker (already in KRaft mode) starts with one command:

Terminal window
docker run -d --name kafka -p 9092:9092 apache/kafka:4.2.2

The command-line scripts live in /opt/kafka/bin inside the container:

Terminal window
# create a topic with 3 partitions
docker exec -it kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic orders --partitions 3
# terminal 1: produce messages in key:value format
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
--topic orders --property parse.key=true --property key.separator=:
# terminal 2: consume from the beginning, showing key and partition
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic orders --from-beginning --property print.key=true --property print.partition=true

Send order-42:created, order-7:created and order-42:paid: both events for order 42 always land on the same partition.

Cluster architecture

  • Broker: a Kafka server. It stores on disk the partitions it is responsible for and serves producers and consumers.
  • Cluster: a set of brokers. In production, at least 3, to tolerate losing one.
  • KRaft: the consensus protocol the brokers themselves (or dedicated nodes, the controllers) use to keep the cluster metadata: which topics exist, who leads each partition, which brokers are alive. It replaced ZooKeeper, which was removed in Kafka 4.0.

Topics, partitions and keys

A topic is a named channel (orders, payments). It is split into partitions, spread across brokers, which distribute the load and increase throughput. Order is only guaranteed within each partition.

Each message has:

  • Key: decides the partition (by hash). Same key, same partition, guaranteed order among them. Without a key, messages are spread across partitions.
  • Value: the content (JSON, Avro, Protobuf).
  • Headers: metadata (trace id, schema version).
  • Timestamp.
How many partitions?

The number of partitions is the parallelism ceiling of a consumer group. You can increase it later, but not decrease it, and increasing it changes which partition each key goes to, breaking ordering during the transition. Start with room to grow (e.g. 6 or 12 for a topic with meaningful traffic).

Retention and compaction
  • By time or size (retention.ms, 7 days by default): old messages are deleted, whether they were read or not.
  • Compaction (cleanup.policy=compact): keeps only the latest message for each key. It turns the topic into a table with the current state (e.g. each customer’s latest address).

Producers

The producer chooses how much acknowledgement it waits for with acks:

acksAcknowledgementRisk
0None: send and move onCan lose messages without knowing
1The leader wrote itLost if the leader dies before replicating
all (or -1)All in-sync replicas (ISR) wrote itThe safest; the default since Kafka 3.0

Since 3.0, the producer is also idempotent by default: network retries don’t create duplicate or out-of-order messages in the partition. For throughput, it groups messages into batches (linger.ms, batch.size) and can compress them (compression.type=zstd).

var props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");
props.put("key.serializer", StringSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());
try (var producer = new KafkaProducer<String, String>(props)) {
// the same key guarantees the order of this order's events
producer.send(new ProducerRecord<>("orders", "order-42", "{\"status\":\"PAID\"}"), (meta, error) -> {
if (error != null) log.error("failed to publish", error);
});
}

Consumers

Consumer groups

Consumers with the same group.id split the partitions among themselves: each partition is read by one consumer in the group. Different groups read everything, independently.

Consumer group: billing

Consumer group: analytics

Topic orders

key=order-42

key=order-7

Producer

Partition 0

Partition 1

Partition 2

Consumer C

reads all 3 partitions

Consumer A

Consumer B

Consumer group: billing

Consumer group: analytics

Topic orders

key=order-42

key=order-7

Producer

Partition 0

Partition 1

Partition 2

Consumer C

reads all 3 partitions

Consumer A

Consumer B

Consequence: more consumers than partitions in a group doesn’t increase throughput; the extra ones sit idle, on standby.

Offsets and commit

The consumer reads in a loop (poll) and commits how far it has processed. The committed offset is stored in Kafka itself, and it is where the group resumes after a restart.

props.put("group.id", "billing");
props.put("enable.auto.commit", "false"); // commit only after processing
try (var consumer = new KafkaConsumer<String, String>(props)) {
consumer.subscribe(List.of("orders"));
while (true) {
var records = consumer.poll(Duration.ofMillis(500));
for (var record : records) {
process(record.key(), record.value());
}
consumer.commitSync();
}
}

With Spring Boot, the loop, the commit and the threads are handled by the framework:

@KafkaListener(topics = "orders", groupId = "billing")
void onOrder(ConsumerRecord<String, String> record) {
process(record.key(), record.value());
}
Rebalance

When a consumer joins, leaves or hangs, the group redistributes the partitions. Since Kafka 4.0, a new rebalance protocol (KIP-848) is production-ready: coordinated by the broker, it moves only the partitions that need to move instead of stopping the whole group. It is already enabled on the brokers, but on the client the default is still the classic protocol: to use the new one, set group.protocol=consumer.

Lag

Lag is the distance between the last message in the topic and the offset committed by the group: how much work is piling up. It is a consumer’s most important metric.

Terminal window
docker exec -it kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group billing

Replication

  • Replication factor: how many copies of each partition exist in the cluster, counting the main one. The usual value is 3.
  • Leader: each partition has a leader, which receives the writes. The other copies (followers) replicate from it.
  • ISR (in-sync replicas): the replicas that are caught up with the leader. If the leader dies, an ISR replica takes over, and the topic stays available.
  • min.insync.replicas=2 with acks=all: a write is only acknowledged if at least two copies have it. With a factor of 3, the cluster can lose a broker without losing data or stopping.

Broker 3

Broker 2

Broker 1

replicates

replicates

Producer

Partition 0

leader

Partition 0

follower

Partition 0

follower

Broker 3

Broker 2

Broker 1

replicates

replicates

Producer

Partition 0

leader

Partition 0

follower

Partition 0

follower

Delivery guarantees

SemanticsHow it happensEffect
At most onceCommit the offset before processingA failure midway loses the message
At least onceCommit after processing (the recommended default)A failure midway reprocesses the message
Exactly onceKafka transactions: read, process and write to another topic atomicallyOnly holds inside Kafka; external effects (email, another database) can still repeat

In practice, use at least once and make the consumer idempotent: processing the same message twice must not cause a duplicate effect (e.g. store the id of events already processed). See the glossary.

Queues in Kafka: share groups

Topics and classic queues solve different problems:

Topic with consumer groupQueue (share group or RabbitMQ)
DistributionA whole partition per consumerMessage by message, to any free consumer
ParallelismCapped by the number of partitionsIndependent of partitions
OrderGuaranteed per partitionNot guaranteed
AcknowledgementBy offset (everything up to there)Individual, per message
Good forEvents, streaming, reprocessing, per-key orderingDistributing tasks among workers

Since Kafka 4.2 (February 2026), share groups bring queue semantics into Kafka. Each message is delivered to a consumer with a temporary lock, and the consumer answers:

  • ACCEPT: processed, don’t deliver again.
  • RELEASE: temporary failure, hand it back for another consumer to try.
  • REJECT: can’t be processed, discard it.

If the lock expires without an answer, the message goes back to the queue, and after a few attempts (5 by default) it is discarded, which keeps a broken message from looping forever. In Java, the client is KafkaShareConsumer.

Ecosystem

  • Kafka Connect: moves data between Kafka and other systems without code, using connectors. Combined with Debezium, it does CDC (change data capture): every change to a database table becomes an event.
  • Kafka Streams: a Java library to process streams (filter, aggregate, join topics) inside your application.
  • Schema Registry (Confluent, outside the Apache project): stores schemas (Avro, Protobuf, JSON Schema) and stops a producer from publishing something that breaks consumers.
  • Apache Flink: large-scale stream processing, with state and time windows.

Production patterns

  • Outbox: to publish an event without risking inconsistency with the database, write it to a table in the same transaction and let another process publish it. See the Transactional Outbox note.
  • Retry and dead letter topic: a failing message must not block the partition. Send it to a retry topic with a delay and, after N attempts, to a dead letter topic, with an alert.
  • Idempotent consumers: assume every message can arrive twice.
  • Monitor the lag of every consumer group, not just broker health.
  • Versioned contracts: evolve schemas in a compatible way; never change the meaning of an existing field.

When to use it

Use KafkaPrefer something else
Events that several systems need to consumeFew messages and a single consumer (a simple queue is enough)
Reprocessing history (new consumer, bug fix)Synchronous request and response (use HTTP or gRPC)
High volume: logs, metrics, clicks, database CDCA small team with nobody to run a cluster (consider a managed service)

See also: System design, CQRS and Observability.

Videos

  • What is Apache Kafka? (Tim Berglund, Confluent): the concepts with great analogies (the video embedded at the top). It is from 2019, so it still talks about ZooKeeper, which has since been replaced by KRaft.
  • Apache Kafka in 6 minutes (James Cutajar): partitions, offsets and replication with an example, very to the point.

References

1/15 · Why Kafka