Kafka Streams
Data Processing
Programming
Event-driven architecture
Application Development

Kafka Streams Punctuate vs Process

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Kafka Streams, process handles incoming records one at a time, while scheduled punctuation handles work that should happen on a time boundary. If you mix those responsibilities, you usually end up with state stores that never flush at the right time or logic that runs only when new data arrives.

What process Does

The process method belongs to the Processor API and runs for each record delivered to the processor node. It is the right place for record-driven logic such as validation, enrichment, routing, counters, and state updates tied directly to an input event.

java
1import org.apache.kafka.streams.processor.api.Processor;
2import org.apache.kafka.streams.processor.api.ProcessorContext;
3import org.apache.kafka.streams.processor.api.Record;
4
5public class CountingProcessor implements Processor<String, String, String, Long> {
6    private ProcessorContext<String, Long> context;
7    private long count = 0L;
8
9    @Override
10    public void init(ProcessorContext<String, Long> context) {
11        this.context = context;
12    }
13
14    @Override
15    public void process(Record<String, String> record) {
16        count++;
17        context.forward(record.withValue(count));
18    }
19
20    @Override
21    public void close() {
22    }
23}

This code reacts only when a record arrives. If the topic is idle, nothing happens.

What Punctuation Is For

Punctuation is for scheduled work. In older discussions you will see the term punctuate; in current Kafka Streams code you normally schedule a Punctuator through context.schedule(...).

That scheduled callback is useful for tasks such as flushing aggregates, expiring stale entries, emitting heartbeats, or checking time-based windows.

java
1import java.time.Duration;
2import org.apache.kafka.streams.processor.PunctuationType;
3import org.apache.kafka.streams.processor.api.Processor;
4import org.apache.kafka.streams.processor.api.ProcessorContext;
5import org.apache.kafka.streams.processor.api.Record;
6
7public class BufferedProcessor implements Processor<String, String, String, String> {
8    private ProcessorContext<String, String> context;
9    private final StringBuilder buffer = new StringBuilder();
10
11    @Override
12    public void init(ProcessorContext<String, String> context) {
13        this.context = context;
14        context.schedule(Duration.ofSeconds(10), PunctuationType.WALL_CLOCK_TIME, timestamp -> {
15            if (buffer.length() > 0) {
16                context.forward(new Record<>("batch", buffer.toString(), timestamp));
17                buffer.setLength(0);
18            }
19        });
20    }
21
22    @Override
23    public void process(Record<String, String> record) {
24        if (buffer.length() > 0) {
25            buffer.append(',');
26        }
27        buffer.append(record.value());
28    }
29
30    @Override
31    public void close() {
32    }
33}

Here, process collects input, and the scheduled callback decides when to emit.

Stream Time vs Wall-Clock Time

The scheduling mode matters.

STREAM_TIME advances only when records arrive. If the input topic is quiet, scheduled callbacks do not fire. Use this when your logic should be aligned with event progress.

WALL_CLOCK_TIME follows real elapsed time on the machine running the task. Use this for operational tasks such as periodic flushing or cleanup that should continue even during low traffic.

A lot of confusion comes from choosing STREAM_TIME and then expecting a callback every ten seconds of real time. That is not how it behaves.

How to Decide

Use process when the operation is about one record.

Use scheduled punctuation when the operation is about time, accumulated state, or maintenance.

Many processors need both: process to update state, and a scheduled callback to emit or prune that state. Separating those roles makes code easier to reason about and easier to test.

Common Pitfalls

  • Expecting scheduled work to happen inside process without new records arriving. Record-driven code is idle when the topic is idle.
  • Using STREAM_TIME when you really need wall-clock behavior. Quiet partitions then appear to "skip" punctuation.
  • Putting expensive batch work directly inside process, which increases per-record latency.
  • Forgetting that punctuation usually acts on stored state, not on a current input record.
  • Reading old examples that talk about overriding punctuate directly without adapting them to the current schedule(...) style.

Summary

  • 'process is triggered by each incoming record.'
  • Scheduled punctuation is triggered by time, not by a specific input record.
  • Use STREAM_TIME for event-progress semantics and WALL_CLOCK_TIME for real elapsed time.
  • Many real processors combine both approaches cleanly.
  • Choose the mechanism based on what should trigger the work, not on which method name sounds convenient.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.