smaple.tr
Kafka Flink stream processing

Kafka Flink Stream Processing: Windowing, CEP, and Stateful Pipeline Architecture [2026]

Mehmet Kurtipek
May 4, 2026
12 min read
Kafka Flink stream processing
Apache Kafka
Apache Flink
Kafka Streams
windowing
CEP
stateful processing
stream processing

Kafka and Flink together form the most capable open-source stream processing stack available today. Kafka provides the durable, partitioned event backbone at any scale; Flink provides the stateful processing engine that transforms raw event streams into actionable results with millisecond latency. Understanding how these tools work together — and where each one's design decisions create constraints — is prerequisite knowledge for any production streaming pipeline.

This guide goes deep on the mechanics: how Kafka partitioning determines processing parallelism, how KStream and KTable abstractions model streaming data, how Flink's windowing and state management handle time-series computation at scale, and how to architect complex event processing patterns for fraud detection and anomaly detection. By the end, you will have the technical foundation to design and implement production Kafka Flink stream processing pipelines.

Kafka and Flink serve distinct roles in a streaming architecture. Kafka is the message backbone — durable, ordered, replayable. Flink is the computation layer — stateful, parallel, fault-tolerant. Neither replaces the other.

Kafka as the Event Backbone

Kafka's data model is simple: topics contain ordered sequences of records. Topics are divided into partitions, each partition hosted on a broker in the Kafka cluster. Partitions are the unit of parallelism and the unit of durability.

Partition assignment determines which consumer reads which partition. A consumer group with N consumers reads from N partitions — each consumer owns a subset of partitions exclusively. Add a consumer instance to increase parallelism, up to the number of partitions. More consumers than partitions means some consumers are idle.

Offset tracking gives every consumer group independent position in every partition. Consumer group A can be at offset 1,000 while consumer group B (reading the same topic) is at offset 8,000. They do not interfere. This is what enables multiple downstream systems to independently process the same event stream.

Retention and replay distinguish Kafka from traditional message queues. Messages are retained for a configurable duration (days to weeks) or indefinitely (with log compaction). If a downstream consumer has a bug and processes events incorrectly, rolling back to an earlier offset and reprocessing from that point is a standard recovery operation — not an exceptional one.

Log compaction (per-topic) retains only the most recent value for each key, discarding earlier values for the same key. A compacted topic converges on a table-like structure: each key has one current value. Compacted topics are the mechanism for changelog-backed KTables in Kafka Streams.

Flink processes events as a continuous stream of records arriving from Kafka (or other sources). The processing topology is a DAG of operators — source, transformations, stateful operators, and sinks. Flink manages this topology across a cluster of TaskManagers, assigning operator subtasks to available slots.

Flink's design distinguishes it from simpler stream processing frameworks in three areas:

Event time with watermarks: Flink tracks event time independently of processing time. Watermarks signal that all events up to a given timestamp have arrived, triggering window computation even when events arrive out of order. This is critical for accurate results with network-delayed or backpressured consumers.

Managed state: Flink maintains operator state (accumulators, aggregates, lookups) in state backends — in JVM heap (HashMapStateBackend) or on local RocksDB (EmbeddedRocksDBStateBackend). State is checkpointed to durable storage (S3, HDFS) at configurable intervals, enabling recovery after failure without reprocessing the entire history.

Exactly-once end-to-end: Flink's two-phase commit sink protocol, combined with Kafka's transactional producer API, achieves exactly-once delivery from Kafka source to Kafka sink. Each read-process-write cycle is atomic; failures recover to the last committed checkpoint.

Kafka Streams API Deep Dive

Kafka Streams runs as a library inside your application — no separate cluster required. The JVM process reads from input topics, processes events using the Streams DSL or Processor API, and writes to output topics. Scaling is done by running more application instances.

KStream: The Event Log Abstraction

KStream models an append-only log of events. Every record is an independent event — a new purchase, a new log entry, a new sensor reading. Records are never updated in-place; each change is a new event with the current value.

KStream operations:

  • filter: drop records not matching a predicate
  • map/mapValues: transform record keys or values
  • flatMap: expand one record to zero or many
  • peek: observe records without modifying them (for side effects like logging)
  • to: write results to an output topic
  • branch: split the stream into multiple streams by predicate
KStream<String, Order> orders = builder.stream("orders");

KStream<String, Order> highValueOrders = orders
    .filter((key, order) -> order.getAmount() > 1000)
    .mapValues(order -> enrichWithRisk(order));

highValueOrders.to("high-value-orders");

KTable: The Changelog Abstraction

KTable models a table of current state — for each key, only the latest value matters. A KTable backed by a compacted topic always reflects the current value of each key. KTable supports lookups, joins, and aggregations that produce a current-state view rather than an event stream.

KTable operations generate materialized state stored in RocksDB locally and backed up to changelog topics in Kafka. If the application restarts, state is restored from the changelog before processing resumes.

KTable<String, Customer> customers = builder.table("customers");

// Enrich order stream with current customer data
KStream<String, EnrichedOrder> enrichedOrders = orders
    .leftJoin(customers,
        (order, customer) -> new EnrichedOrder(order, customer));

The leftJoin with a KTable performs a lookup of the customer's current state at the time each order event arrives. If the customer table is updated between two order events, the second order sees the updated customer state.

Exactly-Once Semantics in Kafka Streams

Setting processing.guarantee=exactly_once_v2 configures Kafka Streams to use the Kafka transactional API. The read-process-write cycle becomes atomic:

  1. Read messages from input partitions
  2. Execute processing logic, update local state stores
  3. Write results to output topics and state changelog topics as a single Kafka transaction
  4. Commit input offsets atomically with the output transaction

If the application fails after step 2 but before step 4, the transaction is aborted on restart. Input messages are re-read and re-processed. Output topics and state stores are restored to the last committed state. From the consumer's perspective, each event was processed exactly once.

The cost: transaction coordination overhead reduces throughput by 10–30% compared to at-least-once. Use exactly-once for financial calculations and user-visible metrics; use at-least-once with idempotent sinks for log aggregation and monitoring data.

Interactive Queries

Kafka Streams exposes state store contents via REST API queries. The stream application itself becomes the source of truth for current-state queries, eliminating the need for a separate database for real-time state:

ReadOnlyKeyValueStore<String, Long> pageViewCounts =
    streams.store(StoreQueryParameters.fromNameAndType(
        "page-view-counts",
        QueryableStoreTypes.keyValueStore()));

Long viewCount = pageViewCounts.get("homepage");

For distributed deployments (multiple application instances), Kafka Streams routes interactive queries to the instance that holds the relevant partition. This enables building queryable streaming applications where each instance handles a fraction of the total state.

Flink handles use cases that Kafka Streams cannot: complex event patterns across multiple streams, very large state (terabytes), sub-millisecond latency requirements, and native Python API for data science teams.

Flink's windowing system supports four window types:

Tumbling windows partition the stream into non-overlapping, fixed-size time slots:

stream
    .keyBy(event -> event.getUserId())
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new CountAggregator());

Every 5 minutes of event time, a new count is emitted per user. The window closes when the watermark passes the window's end timestamp.

Sliding windows produce a result for every slide interval, each window covering the last window-size period. A 10-minute window with a 1-minute slide produces 10 overlapping windows for each event — one count-update per minute, each reflecting the last 10 minutes. Memory and CPU usage scale with the overlap ratio (window-size / slide-interval = 10x in this example).

Session windows close after a gap of inactivity:

stream
    .keyBy(event -> event.getUserId())
    .window(EventTimeSessionWindows.withGap(Time.minutes(30)))
    .process(new SessionProcessor());

A user session is the sequence of events between any two idle periods exceeding 30 minutes. Session windows produce variable-length windows that match natural user behavior patterns — they are the correct window type for funnel analysis, user journey tracking, and engagement metrics.

Global windows collect all events for a key in a single window, emitting results only when a trigger fires. Useful for running totals, all-time high scores, and custom aggregations that do not map to time-based windows.

State Backends and Scaling

State backend choice determines throughput, latency, and maximum state size:

HashMapStateBackend stores all state in JVM heap. Access is fast (direct memory read), but heap size limits total state per TaskManager (typically 8–30 GB with GC pause constraints). Use for small-to-medium state requirements with latency-sensitive workloads.

EmbeddedRocksDBStateBackend stores state on local disk using RocksDB. Supports terabytes of state per node. Access latency is higher (disk I/O vs. memory), but the scale ceiling is dramatically higher. Use for large aggregation state, large join tables, or deduplication state that exceeds available heap.

Parallelism is the primary scaling lever in Flink. Each operator can have an independent parallelism setting:

DataStream<Event> stream = env
    .addSource(kafkaSource).setParallelism(24)  // 24 Kafka partitions
    .keyBy(Event::getUserId)
    .window(TumblingEventTimeWindows.of(Time.seconds(10)))
    .aggregate(new UserAggregator()).setParallelism(16);  // 16 aggregation tasks

Keyed operations (keyBy + window + aggregate) can parallelize at any degree. Non-keyed operations (global aggregations, single-output sinks) are limited to parallelism 1 unless explicitly designed for parallelism.

Checkpointing Configuration

Checkpoint frequency is the primary fault tolerance tuning parameter:

env.enableCheckpointing(30_000);  // Every 30 seconds
env.getCheckpointConfig().setCheckpointingMode(EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(5_000);
env.getCheckpointConfig().setCheckpointTimeout(60_000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);

More frequent checkpoints mean faster recovery (less data to reprocess after failure) but higher overhead during normal operation. Checkpoint size grows with state size — a 10 GB state store creates 10 GB checkpoint artifacts per interval. For large-state applications, 30–60 second intervals balance recovery time against checkpoint overhead.

Savepoints are manually triggered checkpoints that persist indefinitely. They enable: upgrading application code without data loss (stop with savepoint, deploy new version, restore from savepoint); rescaling parallelism; migrating to different infrastructure.

Flink's CEP library detects patterns across sequences of events. CEP is the tool for fraud detection, anomaly detection, and behavioral trigger logic that requires detecting sequences, not just individual events.

Defining Patterns

Pattern<Transaction, ?> fraudPattern = Pattern
    .<Transaction>begin("first-transaction")
    .where(t -> t.getAmount() > 500)
    .followedBy("second-transaction")
    .where(t -> t.getAmount() > 500)
    .within(Time.minutes(10));

This pattern detects two transactions over $500 within 10 minutes on the same account. Patterns can express:

  • Sequence: event A followed by event B
  • Non-occurrence: event A not followed by event B within a time window
  • Repetition: event A occurring N or more times
  • Time constraints: patterns must complete within a duration
  • Conditions: value-based predicates on each event in the pattern

Pattern Matching and Output

PatternStream<Transaction> patternStream =
    CEP.pattern(transactions.keyBy(Transaction::getCardId), fraudPattern);

DataStream<Alert> alerts = patternStream.select(
    (Map<String, List<Transaction>> pattern) -> {
        Transaction first = pattern.get("first-transaction").get(0);
        Transaction second = pattern.get("second-transaction").get(0);
        return new Alert("Rapid large transactions", first, second);
    });

When the pattern matches, the select function receives the matched events and produces an output record. Timeout patterns (events that started matching but did not complete) are handled via flatSelect with a separate timeout output.

Criterion Kafka Streams Apache Flink Spark Structured Streaming
Deployment model Library (embedded) Separate cluster Spark cluster
Minimum latency ~100ms ~10ms ~100ms (micro-batch)
State scale RocksDB (TBs) RocksDB (TBs) Limited by Spark memory
CEP support No Yes (CEP library) Limited
Python API No (Java/Scala only) Yes (PyFlink) Yes (PySpark)
Operational complexity Low High Medium
Exactly-once Yes Yes Yes
SQL support Limited Flink SQL (advanced) SparkSQL (strong)

Kafka Streams is the right choice when the application is already JVM-based, operational simplicity is paramount, and state requirements fit within a single machine's RocksDB storage. Flink handles everything Kafka Streams cannot: Python workloads, CEP, cross-stream joins at high scale, and sub-100ms latency targets. Spark is appropriate when the organization already operates Spark and wants to add streaming with minimal new infrastructure.

Production Deployment on Kubernetes

Smart Maple's stream processing deployments run Flink on Kubernetes using the Flink Kubernetes Operator. The operator manages job lifecycle (submission, scaling, checkpoint coordination), exposes job health as Kubernetes conditions, and handles JobManager HA through Kubernetes leader election.

Kafka Streams applications deploy as standard Kubernetes Deployments. For stateful Kafka Streams applications with RocksDB, using StatefulSets with persistent volume claims ensures that state survives pod restarts without full changelog replay.

Consumer lag-based auto-scaling via KEDA (Kubernetes Event-Driven Autoscaling) adjusts consumer replica counts based on Kafka consumer group lag. Replicas scale up when lag grows (throughput needed), and scale down during off-peak periods. This automation eliminates manual scaling interventions and reduces compute costs by 30–60% compared to fixed-size deployments.

Conclusion

Kafka and Flink are complementary, not competing. Kafka provides the durable event log that decouples producers from consumers and enables replay. Flink provides the stateful, time-aware computation that transforms raw events into actionable outputs. The combination handles use cases from simple filtering and aggregation to complex fraud pattern detection at millions of events per second.

Start with Kafka alone — many real-time requirements can be met with a well-structured consumer group writing to a database before adopting a full stream processing framework. When stateful aggregation, windowing, or CEP patterns are required, Kafka Streams is the lowest-friction path if your application is JVM-based. When complexity, Python requirements, or scale exceeds Kafka Streams' boundaries, Apache Flink is the production-grade answer.

Related Articles

August 11, 2026

MLOps Guide: Taking Machine Learning Models to Production [2026]

87% of machine learning models built by data science teams never reach production. The models work — they pass cross-validation, they score well on holdout sets, they demonstrate genuine predictive value. The problem is not the modeling. The problem is everything that happens between a notebook experiment and a reliable, monitored, production system. MLOps is the discipline that closes that gap. This guide covers the full MLOps stack: maturity levels, tooling choices (MLflow, DVC, Kubeflow

Read More: MLOps Guide: Taking Machine Learning Models to Production [2026]
August 10, 2026

LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]

General-purpose LLMs are impressive. They can write code, summarize documents, answer questions, and translate between languages with reasonable accuracy. But "reasonable" is not good enough when your application requires consistent output format, domain-specific terminology, a particular tone, or behavior that the base model was never trained to exhibit. That gap is where fine-tuning matters. Fine-tuning updates a model's weights on your specific data, changing how the model behaves — not

Read More: LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]
August 9, 2026

Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]

Computer vision has moved well past the research phase. The models are trained, the frameworks are mature, the hardware is accessible, and the use cases are generating measurable returns. What was a specialized capability requiring deep expertise in 2018 is now deployable infrastructure — if you know which component to reach for and where the real complexity lives. This guide covers computer vision applications across industrial, medical, logistics, and document processing domains. It expl

Read More: Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]