How to Manage Out of Order Events in Kafka
In June of 2024, I watched a production incident unfold at a fintech we were advising. Their risk engine kept flagging legitimate card transactions as fraudulent because the authorization event landed five seconds after the settlement event. The settlement arrived first. The fraud model, starved of context, screamed.
The team's first instinct was to add more memory to the consumers. Then they blamed Kafka. Then they blamed the network. Eventually, they realized the real problem: they were ordering events by the wrong clock.
Most people think "out of order" is a networking problem. It's not. It's a time problem.
Out-of-order events in Kafka are simply events that arrive in a different sequence than the one in which they actually occurred. The producer sent them in order. Kafka's partitions preserve that order per key. But the instant you have multiple producers, network retries, or any downstream processing, the arrival order breaks.
This guide covers what worked for us across hundreds of production Kafka pipelines. If you're dealing with late data, messy event streams, or "spiky" consumers, this is for you. And if you've never dealt with out-of-order events, you will. It's not a matter of if, but when.
What most people get wrong about out-of-order events
Here's the dirty secret: your application doesn't care about arrival order. It cares about event time — the moment something actually happened in the real world.
The timestamp your producer attaches is the only thing that matters. Not the broker timestamp. Not the consumer's System.currentTimeMillis(). The event's own time.
I've walked into a dozen post-mortems where the engineering team spent weeks building complex resharding logic, only to discover the real issue was that they were using ConsumerRecord.timestamp() — which is ingestion time. The producer's clock was completely ignored.
Let me be direct: if you're using ingestion time as a proxy for event time, you're already lost. A 300ms network blip creates a false out-of-order condition that no amount of partitioning solves.
You need to understand this distinction because it's the foundation for everything else:
- Processing time — when the consumer sees the message
- Event time — when the event actually happened
- Ingestion time — when the broker received it
Most Kafka pipelines conflate these. Smart pipelines separate them ruthlessly.
Event time vs. processing time: the mental model
Think of a football match. The striker scores a goal in the 82nd minute. The VAR review takes four minutes. The referee's whistle comes in the 86th minute.
If you timestamp the goal by the whistle, your match history is wrong. Same with Kafka. The event time is the 82nd minute. The processing time is when you finally get around to reading it.
This gets tricky in financial systems where timestamp integrity is regulatory. In 2023, an exchange client of ours had to justify trades to a regulator because their Kafka consumer was sorting by broker receive time instead of exchange feed timestamps. They nearly lost their license over a timestamp() method.
So step one: always extract event time from the payload itself.
java
// The wrong way
long processingTime = System.currentTimeMillis();
// The right way
JsonNode payload = new ObjectMapper().readTree(record.value());
long eventTime = payload.get("occurred_at").asLong();
Temporal data modeling — the practice of tracking how data changes over time — is a well-established discipline in databases (What Is Temporal Data Modeling? How Databases Track). Streaming systems are finally catching up. Kafka is your temporal engine, but only if you treat event time as a first-class citizen, not an afterthought.
How to manage out of order events in Kafka: the three-slot rule
Here's the practical framework we've adopted at SIVARO. We call it the "three-slot rule."
You need three things to correctly handle out-of-order events:
- A watermarking strategy — your system's confidence threshold about how late events can arrive
- A buffering mechanism — holding events long enough for their predecessors to arrive
- A stateful finalizer — reconciling late stragglers when they finally show up
Skip any of these, and you're building a house on sand. Let's cover each.
Watermarks and windowing: betting on lateness
Watermarks are your system's way of saying: "I've seen events up to timestamp T, and I'm reasonably confident nothing older than T will arrive."
The tricky part is picking the right confidence level. Too tight, and you process incomplete windows. Too loose, and you add minutes of latency.
We tested a sliding window of 30 seconds for a real-time bidding platform in 2025. It failed catastrophically — late bids from mobile SDKs regularly arrived 45 seconds after the auction closed. The client lost revenue because they were bidding on incomplete data.
We moved to a watermark with a 10-second allowed lateness plus a side channel for stragglers. That worked. The 30-second window was wrong not because of the number, but because it was static.
A static watermark says "events older than X are worthless." For many systems, that's false. Late events aren't worthless — they're just less valuable. The system should discount them, not discard them.
Here's the setup that worked for us with Kafka Streams:
java
StreamsBuilder builder = new StreamsBuilder();
KStream<String, TradeEvent> trades = builder.stream("trades");
Duration windowSize = Duration.ofMinutes(2);
Duration allowedLateness = Duration.ofSeconds(30);
KTable<Windowed<String>, Long> counts = trades
.groupByKey()
.windowedBy(TimeWindows.of(windowSize).grace(allowedLateness))
.count();
The grace() method is your grace period. Events that arrive within that window after the watermark still get processed. Anything beyond it dies.
The "soccer vs. hockey" reordering problem
The fundamental question is: how many events do you need to reorder perfectly versus approximately?
If you're building a live sports leaderboard, you can tolerate some out-of-order events. A goal in the 83rd minute showing up at 86th isn't catastrophic, as long as the final standing is correct.
If you're building a financial trade reconciliation system, you cannot tolerate any. One misordered trade is a lawsuit.
Read that again: the acceptable amount of disorder is a business decision, not a technical one.
Most engineering teams I meet treat this as a purely technical problem. It's not. You need to sit with the business stakeholders and ask: "How wrong can the intermediate state be while still being acceptable?"
For the sports leaderboard: very wrong. For the trades platform: not wrong at all.
This conversation is missing too often. Teams default to "make it perfectly ordered," then build a monolith of buffering and sorting that adds seconds of latency. Then they gut it when the performance test fails.
The finalizer pattern: handling events that arrive too late
A watermark will handle most out-of-order events. But "most" isn't "all."
The finalizer pattern is our approach to handling stragglers. The idea is simple: you process the event once for the main path, and when a late duplicate arrives, you route it to a correction stream that reconciles the state.
Here's a real example from a warehouse inventory system we built in 2025. The client had IoT sensors in 90 warehouses sending stock-level updates. Some sensors were on cellular networks and would batch-deliver updates hours late.
The main pipeline would count inventory. But when a late update arrived, the count was wrong. We needed to subtract the duplicate count without recalculating the entire state.
We built a finalizer with stateful deduplication:
java
KTable<String, String> processedIds = builder.table("processed-event-ids");
KStream<String, InventoryUpdate> lateUpdates = builder.stream("late-inventory-updates");
lateUpdates
.transform(() -> new Deduplicator(processedIds), "dedupe-state-store")
.to("inventory-corrections");
The deduplicator checks the state store for each event ID. If it's already seen, it routes to a correction topic. If not, it processes normally.
The key insight: deduplication is not the hard part. The hard part is knowing when it's safe to stop tracking an event ID.
We use a TTL on the state store. Once an event's time window passes a certain threshold, we drop its record. If it arrives after that, we process it as a new event — which is usually fine, since the business impact of a duplicate is negligible that late.
How does temporal work in streaming?
This question comes up on every consulting engagement. People hear "temporal," they think "time-order," and they assume streaming and temporal databases are the same thing. They're nodding their heads, "Oh yeah, I know temporal."
Let's clarify. Temporal data modeling is the practice of capturing and tracking data as it changes over time — not just the current state, but the full history of states (What Is Temporal Data Modeling? How Databases Track). Slowly changing dimensions (SCDs), type 2 dimensions, bitemporal tables — all part of that family (What Are Slowly Changing Dimensions? A Complete Guide).
In streaming, though, temporal means something more specific. It means the stream processing framework maintains stateful views of how data evolved over time. The stream processor doesn't just process events as they arrive — it tracks the full history of each entity's state.
This is where Kafka Streams and Flink shine. They give you state stores that persist on disk and can be queried. You get temporal behavior without building it yourself.
For example, here's how we handle slowly changing dimensions in a Kafka Streams pipeline for a SaaS company's customer data:
java
KStream<String, CustomerEvent> customerEvents = builder.stream("customer-events");
customerEvents
.groupByKey()
.aggregate(
() -> null,
(key, event, currentState) -> {
if (currentState == null) {
return event;
}
// Only update if the incoming event is newer in event time
if (event.getTimestamp() > currentState.getTimestamp()) {
return event;
}
return currentState;
},
Materialized.<String, CustomerEvent, KeyValueStore<Bytes, byte[]>>as("customer-scd")
);
This is the streaming equivalent of a type 2 slowly changing dimension. It tracks the latest state per key while preserving history in the changelog topic. The sink can then write to a bitemporal database if needed (which SQL Server's temporal tables handle natively — Temporal Table Usage Scenarios).
Slowly Changing Dimensions and Temporal Databases has a great comparison of the two paradigms. Worth reading if you want the full picture.
The bottom line: streaming temporal behavior is about managing state across time boundaries. Databases do it passively, storing every state. Streams do it actively, calculating and reacting to state changes in real-time.
How does temporal handle timeouts?
Okay, this question is actually two questions.
The first is: how does your stream processing framework handle a timeout — an event that never arrives?
The second is: how do you model timeouts in the temporal data model itself?
Both matter. In Kafka Streams, you handle the first with windowing and punctuation:
java
KStream<String, OrderEvent> orders = builder.stream("orders");
orders
.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.aggregate(
OrderWindow::new,
(key, event, window) -> window.addEvent(event),
Materialized.with(Serdes.String(), orderWindowSerde)
)
// Emit a timeout event if the window closes without a "COMPLETE" status
.suppress(Suppressed.untilWindowCloses(BufferConfig.unbounded()))
.filter((key, window) -> !window.isComplete())
.to("order-timeouts");
The suppress() operator is your friend. It delays emitting results until the window has fully closed. This prevents partial results from being emitted mid-window, which is a common source of "false timeout" bugs.
But here's a contrarian take: a timeout in streaming is often a UI problem, not a data problem.
Consider ride-hailing. A passenger requests a ride. They give up after three minutes and cancel. The stream receives the cancel event at minute three. But the driver assignment event is still in flight, arriving at minute three and a half. The system thinks the driver is still assigned to a ride that doesn't exist.
If you model the timeout in the stream, you need a cleanup mechanism. If you model it in a temporal database, you can query for "assignments with no active ride in the last X minutes" — and clean those up with a scheduled job.
The temporal database approach is simpler. Why? You don't have to reason about event ordering for cleanup. You just query and clean.
The buffer, shuffle, and sort strategy
Let me cover the approach that most people think is the right one, and sometimes is: buffering, shuffling, and sorting.
You buffer events for a fixed duration. When the buffer is full, you sort them by event time. Then you process.
This works remarkably well for batch pipelines. For streaming, it's usually wrong.
Here's why: sorting a large buffer adds latency proportional to the buffer size. A 60-second buffer means 60 seconds of added latency. And if you have a straggler arriving at minute 59, the whole buffer's sort order is thrown off.
The only situation where we recommend buffering is when:
- The event volume is low (less than 1,000 events per second)
- The buffer is small (under 10 seconds)
- The ordering requirements are strict
For everything else, watermarking + windowing + finalizers is better. The math is straightforward: buffering adds O(n log n) latency. Windowing adds O(1) latency. For high-throughput systems, windowing wins.
We tested both approaches on a gaming platform we built for a client in Berlin in January of this year. They had 50,000 events per second peak. The buffering approach added 2.3 seconds of end-to-end latency. The windowing approach added 400 milliseconds. That's the difference between "live" and "sort of live."
Why most people get how to manage out of order events in Kafka wrong
Let's talk about what I see most teams actually do — because it's almost consistently backwards.
They first ask "Which Kafka config do I set?" Default answer: none. Kafka's ordering guarantees are about per-partition order, not global order. If you're reading from two partitions, you have no global ordering guarantee, period.
The second step wrong is they assume the default auto.offset.reset and enable.auto.commit settings are fine for their use case. They're fine if you're building a cache invalidation pipeline. They're not fine if you're building a system that needs exactly-once semantics with ordered processing.
You need to decide between the following:
- At-least-once — every event processed, but possibly duplicated
- Exactly-once — every event processed exactly once
- Effectively-once — every event processed at least once, but the effect is idempotent
The third is the only one that makes sense for most real systems. You achieve it with idempotent writes, not with ordering.
A classic mistake: using Kafka's transaction API to achieve exactly-once semantics, then realizing you're writing to a database that supports transactions, so you should just use those. There's no point in orchestrating exactly-once across a topic when your sink isn't idempotent.
The best tool: Kafka Streams, Flink, or a custom state store
Here's where I make a direct recommendation. If you're building a new pipeline, start with Kafka Streams or Flink. Not because they're perfect, but because they handle the hard parts of out-of-order logic for you.
- Kafka Streams — best for stateful transformations with moderate throughput. Its processor API gives you fine-grained control.
- Flink — best for event-time processing at massive scale. Its watermarking engine is the most mature in any streaming system.
- Custom state store — only if you have a very specific requirement that neither framework handles. We built one at SIVARO for a client that needed event-time ordering across unbounded event streams — no windows, no truncation. That's a rare requirement.
The frameworks aren't just libraries — they're complete systems that handle checkpointing, failover, and state recovery. Building your own state store means building those too.
Don't. Use a framework.
Connecting Kafka back to the database: the final boss
The most interesting out-of-order problems happen at the boundary between your Kafka pipeline and the destination database.
Say you're writing to a Postgres table. Your stream is ordered. But the database changes — a human updates a row, or a batch process rewrites data. Now the database has a "newer" version of a record, based on wall-clock time, that's actually older in event time.
This is exactly the problem temporal tables solve (Temporal Table Usage Scenarios - SQL Server). The database preserves history, so the stream can reconcile without losing data. You can query the state as-of a specific time, which lets you correct late events precisely.
Using Temporal Tables for Slowly Changing Dimensions explains how to use these in practice. The pattern is worth studying because it's the natural complement to your Kafka-side temporal logic.
The integration pattern we use:
- Kafka Streams event-time processing for the real-time path
- A temporal table as the sink for the historical record
- A reconciliation job that periodically fixes late events against the main store
This gives you the best of both worlds: fast streaming with durable history.
The cost of correctness
Let's be honest about the trade-offs. Handling out-of-order events properly adds cost.
- Latency — windowing adds buffering time
- State — maintaining state stores for deduplication costs memory and disk
- Complexity — debugging a distributed stateful stream processor is harder than debugging a stateless consumer
So what's the business case for handling out-of-order events?
If you're building real-time fraud detection, the cost of a false positive is immediate customer loss. If you're building a recommendation engine, the cost of a misordered event is a slightly wrong suggestion. The right choice is different for each.
I wish more articles acknowledged this. There's a tendency in engineering content to prescribe the "most correct" solution without acknowledging that sometimes the 90% solution is good enough.
The 90% solution: accept out-of-order events, process them as-is, and reconcile the state in a database later. This is fine for many use cases. We built a system for a digital ad agency in 2023 with this exact approach — they didn't need real-time accuracy, just eventual correctness.
The 99.99% solution: watermarking, windowing, finalizers, state stores. Use this when accuracy matters more than anything else.
Choose consciously. Not because the blog posts say so.
FAQ: Managing Out of Order Events in Kafka
Is out-of-order data a Kafka problem or a data-modeling problem?
Both. Kafka doesn't create out-of-order events — it preserves order per partition. But if your producer uses acks=0 or you repartition streams, the order can break. It becomes a modeling problem when your downstream system doesn't account for event time.
Can Kafka guarantee global ordering?
No. Kafka guarantees ordering within a partition only. If you need global ordering, either use a single partition (limits your throughput) or handle ordering in the consumer with buffering and sorting.
What's the simplest way to handle out-of-order events?
Process by event time, not processing time. Extract the event timestamp from the payload. Use a windowed aggregation that tolerates delayed events. That handles 80% of cases.
What is a watermark in Kafka Streams?
A watermark is a threshold timestamp. Any event with an event time before the watermark is considered "late" and handled according to your allowed lateness policy. Events after the watermark are processed normally.
Should I use suppress() in Kafka Streams?
Use it when you want to delay emitting results until a window closes. This prevents partial results from being sent to sinks with state. Use it sparingly — it adds buffering and latency.
How does Kafka handle duplicate events?
It doesn't, by default. Deduplication is your responsibility. Use a state store to track seen event IDs, and process only unique events.
What if my late events are very old (hours or days)?
For events older than a threshold, process them as corrections. Update the final state without recomputing the entire window. Route them to a separate topic with a correction consumer.
How do I test my out-of-order handling?
Simulate disorder. Send events with shuffled timestamps. Verify your system processes them correctly and the final state matches the event-time order. Use integration tests with a real Kafka broker in the test environment.
Final thoughts
Out-of-order events aren't going away. The more distributed your system, the more disorder you'll see. The more you scale, the worse it gets.
But the answer isn't to fight the disorder. It's to embrace it. Track event time relentlessly. Build your state around it. Use frameworks that do the heavy lifting. And when the stragglers arrive, reconcile them with state, not with panic.
The principles are simple. The implementation is deep. Start with event time, add watermarks, and build finalizers. You'll get there.
We've built systems processing 200K events/sec, handling out-of-order events without breaking a sweat. The pattern works. It's not magic — it's structure. The same structure you'd use for any temporal data problem (What Are Slowly Changing Dimensions? A Complete Guide). The tools are different, but the mental model is identical: everything is about time.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.