A fundamental rule of Apache Kafka is that **message order is guaranteed only within a single partition**. There is no global ordering across different partitions in a topic.

If you have an application where events must be processed in the exact order they occurred (like user profile updates, or financial deposit/withdrawal sequences), you must design your architecture to preserve this order.

Visualizing per-partition message ordering and routing in Kafka
Real-World Analogy: Courier Delivery Trucks

Imagine you run a retail delivery company. A customer orders a table in parts: **Part A (table legs)** and **Part B (table top)**.

If you load Part A onto Truck 1 (Partition 1) and Part B onto Truck 2 (Partition 2), the trucks will take different routes. Truck 2 might arrive first, leaving the customer with a table top but no legs.

To ensure Part A is received before Part B, you must load **both packages onto the exact same truck (Partition)** in the correct sequence. The driver will then deliver them in the exact order they were loaded.

Strategies to Handle Ordering in Kafka

1. Single-Partition Topic (Total Ordering)

The simplest way to guarantee global order is to create a topic with only **one partition**.

  • How it works: All records are sent to Partition 0. Only one consumer thread in a group can read from it, guaranteeing absolute sequential processing.
  • Trade-off: It completely eliminates horizontal scalability. Your throughput is bottlenecked by the speed of a single consumer thread. Use this only for low-throughput configuration topics.

2. Key-Based Routing (Per-Entity Ordering)

In most real-world scenarios, you do not need global topic-wide ordering. You only need ordering **for a specific user, transaction, or device**.

  • How it works: Assign a logical key (such as userId or orderId) to your messages. Kafka hashes the key (using the Murmur2 algorithm by default) and routes all messages with that same key to the exact same partition.
  • Benefit: Different users' events are processed in parallel across multiple partitions, but any single user's events are processed in the exact sequence they were produced.
// Producing with a key to guarantee partition routing
String key = "user-id-54321";
String eventPayload = "{\"action\":\"ADD_TO_CART\",\"item\":\"laptop\"}";

// Key-based routing guarantees this goes to the same partition as previous user events
ProducerRecord<String, String> record = new ProducerRecord><(
    "shopping-events", key, eventPayload
);

producer.send(record);

3. Producer Client Configuration

Even with key-based routing, network retries can scramble message order. If the producer sends message 1, it fails, then it sends message 2 successfully, and then retries message 1 successfully, the broker stores them as `[Message 2, Message 1]`.

To prevent this, configure the producer with **idempotence enabled** or restrict flight requests:

# Prevent retry ordering inversion
enable.idempotence=true
max.in.flight.requests.per.connection=5

4. Application-Level Resequencing

If you must read from multiple partitions and sort them, embed a **sequence number** or **timestamp** in the payload. The consumer can buffer incoming messages in a local window (using a tool like Kafka Streams or an in-memory priority queue) and sort them before triggering the database updates.

Conclusion

For high-throughput systems, use **Key-Based Routing** with a unique entity ID as the message key. Combine this with an **Idempotent Producer** configuration to guarantee that network retries never shuffle your event sequences.