Kafka Streams Basics
IntermediateKafka Streams is a Java client library for building stateful, fault-tolerant stream processing applications that read from and write to Kafka topics.
Overview
Kafka Streams is a lightweight, embeddable Java library for building real-time stream processing applications directly on top of Kafka — no separate cluster (Flink, Spark) required. An application processes data by defining a **topology**: a directed acyclic graph of source nodes (reading from topics), processor nodes (transform, filter, aggregate), and sink nodes (writing to topics). Kafka Streams handles fault tolerance through **changelog topics**: state stores are backed by compacted Kafka topics, so state is rebuilt automatically after restart. It integrates with Spring Boot via `spring-kafka` and the `KafkaStreamsConfiguration` API. The high-level **Streams DSL** (`map`, `filter`, `join`, `aggregate`) covers most use cases; the low-level **Processor API** gives full control for custom logic.
Defining a Topology with the Streams DSL
The DSL is built on `StreamsBuilder`. `stream()` creates a `KStream` for record-by-record processing. `table()` creates a `KTable` for keyed state. DSL operations are lazy — no processing happens until the topology is compiled and a `KafkaStreams` instance is started.
@Configuration
@EnableKafkaStreams
class StreamConfig {
@Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
KafkaStreamsConfiguration streamsConfig() {
return new KafkaStreamsConfiguration(Map.of(
StreamsConfig.APPLICATION_ID_CONFIG, "order-processor",
StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "broker:9092",
StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass(),
StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()
));
}
}
@Component
class OrderTopology {
@Autowired
void buildPipeline(StreamsBuilder builder) {
KStream<String, String> orders = builder.stream("orders-raw");
orders
.filter((key, value) -> value.contains(""status":"PAID""))
.mapValues(value -> enrichOrder(value))
.to("orders-enriched"); // sink topic
}
}Stateful Operations: Aggregations and Joins
Stateful operations (count, aggregate, join) store intermediate state in a local **RocksDB state store** backed by a compacted changelog Kafka topic. On restart, Kafka Streams restores the state store from the changelog before resuming processing — providing fault tolerance without external storage.
@Autowired
void buildPipeline(StreamsBuilder builder) {
KStream<String, Order> orders = builder.stream(
"orders",
Consumed.with(Serdes.String(), orderSerde)
);
// Count orders per customer — stateful aggregation
KTable<String, Long> orderCounts = orders
.groupByKey()
.count(Materialized.as("order-counts-store")); // named state store
orderCounts.toStream().to("order-counts-topic",
Produced.with(Serdes.String(), Serdes.Long()));
// KStream-KTable join: enrich order with customer data
KTable<String, Customer> customers = builder.table("customers");
orders.join(customers,
(order, customer) -> enrich(order, customer))
.to("orders-enriched");
}Fault Tolerance, Scaling, and State Restoration
Kafka Streams achieves fault tolerance via changelog topics — every state store write is also sent to an internal compacted topic. On restart, the state is replayed from the changelog. Scaling is done by adding more application instances: Kafka Streams redistributes partitions (and their associated state) across instances automatically via the Streams rebalance protocol.
# Kafka Streams fault tolerance and scaling:
# 1. State store backed by changelog topic (created automatically)
# Internal topic: <app-id>-<state-store-name>-changelog
# e.g.: order-processor-order-counts-store-changelog
# 2. Restart recovery:
# App starts → consumer group rebalance → partitions assigned
# → state store restored from changelog (or standby replica if configured)
# → processing resumes from last committed offset
# 3. Standby replicas — warm spare state (reduces restore time)
num.standby.replicas=1 # keep 1 standby copy in another instance
# 4. Scaling: run 2 instances of the same application-id
# → Kafka Streams splits partitions between them
# → each instance handles its own slice of state
# 5. Interactive Queries — read state store directly (no external DB)
ReadOnlyKeyValueStore<String, Long> store =
streams.store(StoreQueryParameters.fromNameAndType(
"order-counts-store", QueryableStoreTypes.keyValueStore()));
Long count = store.get("customer-42");Key Points to Remember
- 1Kafka Streams is an embedded Java library — no separate processing cluster required
- 2A topology is a DAG of source, processor, and sink nodes compiled from the Streams DSL
- 3Stateful operations use RocksDB state stores backed by compacted changelog topics
- 4On restart, state is restored from the changelog before processing resumes
- 5Scaling: run multiple instances of the same application.id; Kafka redistributes partitions
- 6Interactive Queries expose state store contents directly without an external database
Interview Questions
Sign in to ask AriaHow does Kafka Streams achieve fault tolerance without an external database?
What is the difference between KStream and KTable in Kafka Streams?
How do you scale a Kafka Streams application horizontally?
What is a changelog topic and what is it used for?
What are standby replicas in Kafka Streams and why are they useful?
Ask Aria about Kafka Streams Basics
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.