Streaming Updated: 2026-04-03

What is Stateful Processing?

A stream processing technique where the application remembers information across multiple events in time.

From the field

Stateful processing is where streaming gets genuinely hard. Stateless streams (filter out bad rows, transform JSON into Parquet, route messages to topics) are easy — each event is independent. Stateful streams (running totals per user, sessionization, fraud pattern detection, deduplication, joins over time windows) require remembering what happened in prior events. In a single-process program this is just a variable in memory. In a distributed system processing millions of events/sec across dozens of workers, "a variable" becomes a hard distributed-systems problem: where is state stored? how does it survive worker crashes? how does it move when you scale out? how do you get exactly-once without double-counting?

Why stateful streaming separates the serious frameworks

Kafka Streams, Apache Flink, and Spark Structured Streaming all solve stateful processing — each with its own trade-offs. Flink is widely considered best-in-class: it stores state in a local RocksDB instance (fast), checkpoints snapshots to S3 every few seconds (durable), and during worker rebalances moves state alongside the data (consistent). Kafka Streams embeds RocksDB per-instance and uses a Kafka topic (the changelog) as the durable backing store. Spark uses checkpoints to durable storage. The frameworks differ meaningfully in latency (Flink processes event-by-event; Spark micro-batches every 100ms-few-seconds), state size they can handle (Flink scales to TB of state per worker; Spark is more memory-constrained), and API ergonomics.

Field story

A payments client needed real-time fraud detection: for each transaction, check whether the user had more than 5 transactions in the last 15 minutes, whether the merchant category had changed suddenly, whether the geographic distance from the last transaction was impossible given elapsed time. All stateful — all require keeping per-user state updated event-by-event. They started with Spark Streaming micro-batching every 30 seconds, which meant fraud was caught 30 seconds late — unacceptable for card-present transactions. We migrated to Flink, keyed on user_id, and stored the last 24 hours of per-user transaction history in keyed state backed by RocksDB. End-to-end latency dropped from 30s to 80ms, throughput held at 12k events/sec per worker, and checkpoints to S3 every 60 seconds gave us crash recovery with no double-processing. State size per worker grew to about 40GB — Flink handled it without noticeable impact.

Stateful Processing is a stream processing paradigm where the application maintains and updates internal state across multiple events. Unlike stateless operations (filtering, mapping) that process each event independently, stateful operations (running sums, session windows, pattern detection) need to remember what happened in previous events to produce correct results.

Stateless vs Stateful

AspectStatelessStateful
MemoryNo memory of past eventsRemembers past events
ExampleFilter events where amount > 100Running total of amounts per user
ComplexitySimpleComplex (state management)
ScalingEasy (just add workers)Hard (state must move with data)
RecoveryInstant (no state to restore)Requires checkpointing

Why State is Hard in Distributed Streaming

In a single-machine program, state is just a variable. In a distributed streaming system processing millions of events per second across dozens of machines, state becomes the hardest problem:

  • Where to store state: Must be fast (in-memory or local SSD) but also durable
  • Partitioning: State for user A must always be on the same worker that processes user A's events
  • Rebalancing: When workers are added/removed, state must move without losing data
  • Fault tolerance: If a worker crashes, its state must be recoverable
  • Exactly-once: State updates must be atomic with event processing

Types of State

Keyed State

State partitioned by a key (most common):
  • Per-user session: Track events for each user independently
  • Per-account balance: Running total for each account
  • Per-device metrics: Aggregate sensor readings per device

Operator State

State shared across all keys in a single operator instance:
  • Kafka offsets: Track which messages have been consumed
  • Broadcast state: Configuration data shared with all workers

State Backends

BackendSpeedCapacityBest For
In-Memory (HashMap)FastestLimited by RAMSmall state, low latency
RocksDBFast (SSD)Very large (TB+)Large state, production workloads
Remote (Redis)Network latencyUnlimitedShared state across applications
Apache Flink uses RocksDB as its default production state backend — it can manage terabytes of state per worker.

Checkpointing: Fault Tolerance for State

Checkpointing periodically saves a consistent snapshot of all state to durable storage (S3, HDFS):

Normal processing:
  Events → [State: counter=42] → Output

Checkpoint taken:
  State snapshot → S3 (counter=42, offset=1000)

Worker crashes and recovers:
  Restore from S3 → [State: counter=42] → Replay from offset 1000

Flink's checkpointing provides exactly-once state guarantees — even during failures, each event affects state exactly once.

Stateful Operations in Practice

  • Windowed aggregations: Sum of sales in 5-minute tumbling windows
  • Session windows: Group user activity events separated by 30-min idle gaps
  • Pattern detection (CEP): Detect 'login failed 3 times in 5 minutes' patterns
  • Running aggregates: Continuously updated average, count, or percentile
  • Joins: Join two event streams by key within a time window
  • Deduplication: Remember seen event IDs to filter duplicates

Frameworks with Strong Stateful Support

FrameworkState ManagementCheckpointing
Apache FlinkBest-in-class (RocksDB, exactly-once)Automatic, consistent
Kafka StreamsGood (RocksDB, changelog topics)Via Kafka compacted topics
Spark Structured StreamingBasic (in-memory, HDFS)Micro-batch boundaries
Apache BeamPortable (runner-dependent)Runner-dependent

Key Points

Gotchas

Stateful-processing pitfalls that teams hit after the demo works:

  • State growing unboundedly. "Track per-user counters" seems fine until you have 500M unique users and the state grows to TB. Always set a TTL (time-to-live) on state entries — Flink's StateTtlConfig, Kafka Streams' windowed state retention — or you'll blow up RAM/RocksDB/disk.
  • Wrong key choice causing hot partitions. If state is keyed on something skewed (like country='US'), one worker handles most state while others idle. Choose keys with high cardinality and uniform distribution; use compound keys or hash-salting for skew.
  • Checkpoint interval too long. Default Flink checkpoint interval is conservative; if checkpoints take 2 minutes and fail mid-stream, you replay 2 minutes of events on recovery. Tune checkpoint interval based on acceptable replay time and checkpoint cost.
  • Backpressure from slow checkpoints. Checkpointing large state (GB-TB) puts pressure on the pipeline; if checkpoints can't complete within the interval, the pipeline stalls. Use incremental checkpointing (RocksDB) and monitor checkpoint duration.
  • Ignoring event-time watermarks. Stateful aggregations over time windows need watermarks to know "all events for this window have arrived." Misconfigured watermarks (too aggressive → lost late events; too lax → windows never close) produce incorrect results silently.
  • Savepoint/checkpoint schema evolution. Restoring state after changing the keyed-state type or schema fails or produces garbage. Always use Flink's schema evolution support (Avro, POJOs with default constructors) and test restore before deploying breaking changes.
  • State backends chosen for the wrong reason. In-memory hashmap backend is fast but limited by heap; RocksDB backend scales to TB but adds serialization overhead. Teams sometimes pick hashmap for speed on dev data, discover in prod that it OOMs, and switch to RocksDB — a painful migration.
  • Exactly-once that isn't. Flink/Kafka-Streams exactly-once applies to state + output within the system. If your sink is not transactional (writing to a non-transactional database or HTTP API), you're at at-least-once for external side effects. Plan idempotent writes accordingly.
  • Windowed operations on non-keyed state. Windows require a key (group-by); teams sometimes try to compute a global tumbling window and discover it all funnels to one worker. Use keyed windows or explicit global windows with care.
  • Local state without remote backup. Running in Kubernetes and the pod gets evicted: without checkpointing to durable remote storage (S3/HDFS), state is gone. Always checkpoint to remote durable storage for any production stateful job.

Frequently Asked Questions

What is stateful stream processing?

Stateful stream processing means the application maintains information about past events while processing new ones. Examples: running total of spending per user, current session state, "last seen" timestamp per device, count of login failures in the last 5 minutes, deduplication tables. Each incoming event reads and updates this state, so the output depends not just on the current event but on history. This contrasts with stateless processing (filter, map) where each event is independent. Stateful processing is what enables sessionization, pattern detection, real-time aggregations, and streaming joins — essentially, anything interesting in real-time analytics requires state.

Why is stateful processing harder than stateless?

Stateful processing requires solving distributed-systems problems that stateless doesn't. State must be: (1) partitioned so events with the same key always go to the same worker (otherwise you'd see different views of the counter); (2) durable — if a worker crashes, state can't vanish; (3) recoverable — after a crash, state must be restored so processing resumes correctly; (4) scalable — state must move when you add/remove workers; (5) consistent — state updates must be atomic with event processing for exactly-once guarantees. Stateless processing has none of these concerns because there's no state to protect. The hard problems in production streaming systems are almost always state-related.

What is checkpointing in stream processing?

Checkpointing is the mechanism that makes stateful streaming fault-tolerant. Periodically (e.g., every 60 seconds), the framework takes a consistent snapshot of all worker state plus the input offsets at that moment and writes it to durable remote storage (S3, HDFS, GCS). If a worker crashes, the framework restarts workers from the latest checkpoint — state is restored, and processing resumes from the saved offsets. This gives exactly-once state semantics: no event affects state twice and no state update is lost. Flink's checkpointing is incremental (only changed RocksDB SST files are written), keeping the cost reasonable even with TB of state.

Which framework is best for stateful processing?

Apache Flink is widely considered the leader for stateful stream processing. Reasons: (1) best-in-class state management with RocksDB scaling to TB of state per worker; (2) automatic, incremental checkpointing with exactly-once guarantees; (3) true event-by-event processing with millisecond latency; (4) mature event-time semantics with watermarks; (5) savepoints for zero-downtime upgrades with state schema evolution. Kafka Streams is good when you're already Kafka-native and want state inside your Java app. Spark Structured Streaming is fine for simpler state needs and Spark-first teams, but lags Flink on latency and state scale. Pick Flink for non-trivial stateful pipelines unless you have specific reasons otherwise.

What is keyed state vs operator state?

Keyed state is partitioned by a key (user_id, device_id, account_id). Each key's state is stored on exactly one worker (determined by `hash(key) % parallelism`), so "user-42's counter" is always accessed consistently. This is the most common type of state and the only way to parallelize stateful processing. Operator state is state attached to each parallel operator instance (not a key) — typical uses: Kafka consumer offsets, broadcast configuration distributed to all workers, buffering state for batched writes. Keyed state is for business logic (per-entity counters/sessions/aggregations); operator state is for framework plumbing and coordination-style needs.

What is a state backend?

A state backend is the storage engine where streaming frameworks keep state. Options in Flink: (1) HashMap backend — in-process Java HashMap on the JVM heap, fastest access but capped by heap size, typically used for small state or testing; (2) RocksDB backend — embedded KV store on local SSD, scales to TB of state per worker, the production default for anything non-trivial, trades some CPU (serialization + LSM compaction) for near-unlimited capacity. Checkpoints from either backend go to remote durable storage (S3/HDFS) for fault tolerance. Choose HashMap for small-state, low-latency workloads; choose RocksDB for production-scale state.

How do you handle state schema evolution?

State schema evolution is handling changes to the data types stored in state when upgrading a streaming job. Flink handles it via: (1) savepoints — triggered stop-point snapshots; (2) supported types with evolution rules — POJOs add/remove/reorder fields, Avro follows standard Avro schema-evolution rules; (3) restart from savepoint on the new job version. Gotchas: changing a keyed-state type fundamentally (e.g., Long to String) is a breaking change and usually requires a state migration job. Always test schema evolution in a staging environment before production, and version-control the state types alongside the job code.

Related Terms

Apache FlinkApache KafkaStream ProcessingData Pipeline

Related Tools

Apache FlinkKafka StreamsSpark StreamingApache BeamRocksDB

Learn More

Related Articles

Browse All Terms Read Articles