
Kafka Stream Processing
- 336 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
Design Kafka producers, consumers, topics, partitioning, and stream joins for reliable real-time event pipelines in production services.
About
Guides Claude through Apache Kafka stream processing: topic modeling, producer/consumer patterns, partitioning, consumer groups, stream joins, windowing, fault tolerance, and production tuning for scalable real-time data pipelines.
- Topic design and partitioning strategy
- Consumer groups and offset management
- Exactly-once and idempotent processing patterns
- Stream joins, windows, and stateful transforms
- Operational tuning for throughput and lag
Kafka Stream Processing by the numbers
- 336 all-time installs (skills.sh)
- +19 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,199 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill kafka-stream-processingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 336 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
What it does
Design Kafka producers, consumers, topics, partitioning, and stream joins for reliable real-time event pipelines in production services.
Files
Kafka Stream Processing
A comprehensive skill for building event-driven applications with Apache Kafka. Master producers, consumers, Kafka Streams, connectors, schema registry, and production deployment patterns for real-time data processing at scale.
When to Use This Skill
Use this skill when:
- Building event-driven microservices architectures
- Processing real-time data streams and event logs
- Implementing publish-subscribe messaging systems
- Creating data pipelines for analytics and ETL
- Building streaming data applications with stateful processing
- Integrating heterogeneous systems with Kafka Connect
- Implementing change data capture (CDC) patterns
- Building real-time dashboards and monitoring systems
- Processing IoT sensor data at scale
- Implementing event sourcing and CQRS patterns
- Building distributed systems requiring guaranteed message delivery
- Creating real-time recommendation engines
- Processing financial transactions with exactly-once semantics
- Building log aggregation and monitoring pipelines
Core Concepts
Apache Kafka Architecture
Kafka is a distributed streaming platform designed for high-throughput, fault-tolerant, real-time data processing.
Key Components:
1. Topics: Named categories for organizing messages 2. Partitions: Ordered, immutable sequences of records within topics 3. Brokers: Kafka servers that store and serve data 4. Producers: Applications that publish records to topics 5. Consumers: Applications that read records from topics 6. Consumer Groups: Coordinated consumers sharing workload 7. ZooKeeper/KRaft: Cluster coordination (ZooKeeper legacy, KRaft modern)
Design Principles:
Kafka Design Philosophy:
- High Throughput: Millions of messages per second
- Low Latency: Single-digit millisecond latency
- Durability: Replicated, persistent storage
- Scalability: Horizontal scaling via partitions
- Fault Tolerance: Automatic failover and recovery
- Message Delivery Semantics: At-least-once, exactly-once supportTopics and Partitions
Topics are logical channels for data streams. Each topic is divided into partitions for parallelism and scalability.
# Create a topic with 20 partitions and replication factor 3
$ bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic my_topic_name \
--partitions 20 --replication-factor 3 --config x=yPartition Characteristics:
- Ordered: Messages within a partition are strictly ordered
- Immutable: Records cannot be modified after written
- Append-only: New records appended to partition end
- Retention: Configurable retention by time or size
- Replication: Each partition replicated across brokers
Adding Partitions:
# Increase partition count (cannot decrease)
$ bin/kafka-topics.sh --bootstrap-server localhost:9092 --alter --topic my_topic_name \
--partitions 40Note: Adding partitions doesn't redistribute existing data and may affect consumers using custom partitioning.
Stream Partitions and Tasks
<h3>Stream Partitions and Tasks</h3>
<p> The messaging layer of Kafka partitions data for storing and transporting it. Kafka Streams partitions data for processing it. In both cases, this partitioning is what enables data locality, elasticity, scalability, high performance, and fault tolerance. Kafka Streams uses the concepts of <b>partitions</b> and <b>tasks</b> as logical units of its parallelism model based on Kafka topic partitions. There are close links between Kafka Streams and Kafka in the context of parallelism: </p>
<ul>
<li>Each <b>stream partition</b> is a totally ordered sequence of data records and maps to a Kafka <b>topic partition</b>.</li>
<li>A <b>data record</b> in the stream maps to a Kafka <b>message</b> from that topic.</li>
<li>The <b>keys</b> of data records determine the partitioning of data in both Kafka and Kafka Streams, i.e., how data is routed to specific partitions within topics.</li>
</ul>
<p> An application's processor topology is scaled by breaking it into multiple tasks. More specifically, Kafka Streams creates a fixed number of tasks based on the input stream partitions for the application, with each task assigned a list of partitions from the input streams (i.e., Kafka topics). The assignment of partitions to tasks never changes so that each task is a fixed unit of parallelism of the application. Tasks can then instantiate their own processor topology based on the assigned partitions; they also maintain a buffer for each of its assigned partitions and process messages one-at-a-time from these record buffers. As a result stream tasks can be processed independently and in parallel without manual intervention. </p>
<p> Slightly simplified, the maximum parallelism at which your application may run is bounded by the maximum number of stream tasks, which itself is determined by maximum number of partitions of the input topic(s) the application is reading from. For example, if your input topic has 5 partitions, then you can run up to 5 applications instances. These instances will collaboratively process the topic's data. If you run a larger number of app instances than partitions of the input topic, the "excess" app instances will launch but remain idle; however, if one of the busy instances goes down, one of the idle instances will resume the former's work. </p>
<p> It is important to understand that Kafka Streams is not a resource manager, but a library that "runs" anywhere its stream processing application runs. Multiple instances of the application are executed either on the same machine, or spread across multiple machines and tasks can be distributed automatically by the library to those running application instances. The assignment of partitions to tasks never changes; if an application instance fails, all its assigned tasks will be automatically restarted on other instances and continue to consume from the same stream partitions. </p>Message Delivery Semantics
Design:
- The Producer: Design considerations.
- The Consumer: Design considerations.
- Message Delivery Semantics: At-least-once, at-most-once, exactly-once.
- Using Transactions for atomic operations.At-Least-Once Delivery:
- Producer retries until acknowledgment received
- Consumer commits offset after processing
- Risk: Duplicate processing on failures
- Use case: When duplicates are acceptable or idempotent processing
At-Most-Once Delivery:
- Consumer commits offset before processing
- No producer retries
- Risk: Message loss on failures
- Use case: When data loss acceptable (e.g., metrics)
Exactly-Once Semantics (EOS):
- Transactional writes with idempotent producers
- Consumer reads committed messages only
- Use case: Financial transactions, critical data processing
Producer Load Balancing
ProducerClient:
publish(topic: str, message: bytes, partition_key: Optional[str] = None)
topic: The topic to publish the message to.
message: The message payload to send.
partition_key: Optional key to determine the partition. If None, random partitioning is used.
get_metadata(topic: str) -> dict
topic: The topic to get metadata for.
Returns: A dictionary containing broker information and partition leader details.The producer directs data to the partition leader broker without a routing tier. Kafka nodes provide metadata to producers for directing requests to the correct partition leaders. Producers can implement custom partitioning logic or use random distribution.
Producers
Kafka producers publish records to topics with configurable reliability and performance characteristics.
Producer API
Producer API:
- send(record): Sends a record to a Kafka topic.
- Parameters:
- record: The record to send, including topic, key, and value.
- Returns: A Future representing the result of the send operation.
- flush(): Forces any buffered records to be sent.
- close(): Closes the producer, releasing any resources.
- metrics(): Returns metrics about the producer.
Configuration:
- bootstrap.servers: A list of host/port pairs to use for establishing the initial connection to the Kafka cluster.
- key.serializer: The serializer class for key that implements the org.apache.kafka.common.serialization.Serializer interface.
- value.serializer: The serializer class for value that implements the org.apache.kafka.common.serialization.Serializer interface.
- acks: The number of acknowledgments the producer requires the leader to have received before considering a request complete.
- linger.ms: The producer groups together any records that arrive in between request transmissions into a single batched request.
- batch.size: The producer will attempt to batch records together into fewer requests whenever multiple records are being sent to the same partition.Producer Configuration
Essential Settings:
1. bootstrap.servers: Kafka cluster connection string
- Format:
host1:9092,host2:9092,host3:9092 - Use multiple brokers for fault tolerance
2. key.serializer / value.serializer: Data serialization
org.apache.kafka.common.serialization.StringSerializerorg.apache.kafka.common.serialization.ByteArraySerializer- Custom serializers for complex types
3. acks: Acknowledgment level
0: No acknowledgment (fire and forget)1: Leader acknowledgment onlyall/-1: All in-sync replicas acknowledge (strongest durability)
4. retries: Retry count for failed sends
- Default: 2147483647 (Integer.MAX_VALUE)
- Set to 0 to disable retries
5. enable.idempotence: Exactly-once semantics
true: Enables idempotent producer (prevents duplicates)false: Default behavior
Performance Tuning:
1. linger.ms: Batching delay
- Default: 0 (send immediately)
- Higher values (5-100ms) increase throughput via batching
- Trade-off: Latency vs throughput
2. batch.size: Batch size in bytes
- Default: 16384 (16KB)
- Larger batches improve throughput
- Maximum single batch size per partition
3. compression.type: Message compression
- Options:
none,gzip,snappy,lz4,zstd - Reduces network bandwidth and storage
- CPU overhead for compression/decompression
4. buffer.memory: Total producer buffer memory
- Default: 33554432 (32MB)
- Memory for buffering unsent records
- Producer blocks when buffer full
Producer Example (Java)
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class SimpleProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 32768);
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
ProducerRecord<String, String> record =
new ProducerRecord<>("my-topic", "key1", "Hello Kafka!");
// Async send with callback
producer.send(record, (metadata, exception) -> {
if (exception != null) {
System.err.println("Error producing: " + exception);
} else {
System.out.printf("Sent to partition %d, offset %d%n",
metadata.partition(), metadata.offset());
}
});
}
}
}Producer Best Practices
1. Use Batching: Configure linger.ms and batch.size for throughput 2. Enable Idempotence: Set enable.idempotence=true for reliability 3. Handle Errors: Implement proper callback error handling 4. Use Compression: Enable compression for large messages 5. Partition Keys: Use meaningful keys for ordered processing 6. Close Producers: Always close producers in finally blocks 7. Monitor Metrics: Track producer metrics (record-send-rate, compression-rate) 8. Resource Pools: Reuse producer instances when possible
Consumers
Kafka consumers read records from topics, supporting both individual and group-based consumption.
Consumer Groups
Consumer groups enable parallel processing with automatic load balancing and fault tolerance.
Key Concepts:
- Group ID: Unique identifier for consumer group
- Partition Assignment: Each partition consumed by one consumer in group
- Rebalancing: Automatic reassignment when consumers join/leave
- Offset Management: Group tracks committed offsets per partition
Consumer Group Monitoring:
# List consumer groups
$ bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
# Describe consumer group members with partition assignments
$ bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group --members --verbose
CONSUMER-ID HOST CLIENT-ID #PARTITIONS ASSIGNMENT
consumer1-3fc8d6f1-581a-4472-bdf3-3515b4aee8c1 /127.0.0.1 consumer1 2 topic1(0), topic2(0)
consumer4-117fe4d3-c6c1-4178-8ee9-eb4a3954bee0 /127.0.0.1 consumer4 1 topic3(2)
consumer2-e76ea8c3-5d30-4299-9005-47eb41f3d3c4 /127.0.0.1 consumer2 3 topic2(1), topic3(0,1)
consumer3-ecea43e4-1f01-479f-8349-f9130b75d8ee /127.0.0.1 consumer3 0 -Consumer Configuration
Essential Settings:
1. bootstrap.servers: Kafka cluster connection 2. group.id: Consumer group identifier 3. key.deserializer / value.deserializer: Data deserialization 4. enable.auto.commit: Automatic offset commits
true: Auto-commit offsets periodicallyfalse: Manual offset management
5. auto.offset.reset: Behavior when no offset found
earliest: Start from beginninglatest: Start from endnone: Throw exception
Consumer-Specific Kafka Streams Defaults:
Parameter Name: max.poll.records
Corresponding Client: Consumer
Streams Default: 100
Parameter Name: client.id
Corresponding Client: -
Streams Default: <application.id>-<random-UUID>
Parameter Name: enable.auto.commit
Description: Controls whether the consumer automatically commits offsets. When true, the consumer will automatically commit offsets periodically based on the poll interval.
Default Value: trueConsumer Example (Java)
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class SimpleConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-consumer-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(Collections.singletonList("my-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Partition: %d, Offset: %d, Key: %s, Value: %s%n",
record.partition(), record.offset(), record.key(), record.value());
// Process record
processRecord(record);
}
// Manual commit after processing batch
consumer.commitSync();
}
}
}
private static void processRecord(ConsumerRecord<String, String> record) {
// Business logic here
}
}Consumer Offset Management
Offset Commit Strategies:
1. Auto-commit (default):
- Simple but risky for at-least-once delivery
- May commit before processing completes
2. Manual Synchronous Commit:
- Blocks until commit succeeds
- Guarantees offset committed before continuing
- Lower throughput
3. Manual Asynchronous Commit:
- Non-blocking commit
- Higher throughput
- Handle failures in callback
4. Hybrid Approach:
- Async commits during processing
- Sync commit before rebalance/shutdown
// Async commit with callback
consumer.commitAsync((offsets, exception) -> {
if (exception != null) {
System.err.println("Commit failed: " + exception);
}
});
// Sync commit for reliability
try {
consumer.commitSync();
} catch (CommitFailedException e) {
System.err.println("Commit failed: " + e);
}Consumer Best Practices
1. Choose Right Auto-commit: Disable for at-least-once semantics 2. Handle Rebalancing: Implement ConsumerRebalanceListener 3. Process Efficiently: Minimize poll() call duration 4. Graceful Shutdown: Close consumers properly 5. Monitor Lag: Track consumer lag metrics 6. Partition Assignment: Understand assignment strategies 7. Thread Safety: Kafka consumers are NOT thread-safe 8. Error Handling: Retry logic for transient failures
Kafka Streams
Kafka Streams is a client library for building real-time streaming applications with stateful processing.
Kafka Streams Architecture
Processor Topology:
There are two special processors in the topology:
<ul>
<li><b>Source Processor</b>: A special type of stream processor that does not have any upstream processors. It produces an input stream to its topology from one or multiple Kafka topics by consuming records from these topics and forwarding them to its down-stream processors.</li>
<li><b>Sink Processor</b>: A special type of stream processor that does not have down-stream processors. It sends any received records from its up-stream processors to a specified Kafka topic.</li>
</ul>
Note that in normal processor nodes other remote systems can also be accessed while processing the current record. Therefore the processed results can either be streamed back into Kafka or written to an external system.Sub-topologies:
Applications are decomposed into sub-topologies connected by repartition topics. Each sub-topology can scale independently.
KStream vs KTable vs GlobalKTable
KStream: Immutable stream of records
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.kstream.KStream;
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Long> wordCounts = builder.stream(
"word-counts-input-topic", /* input topic */
Consumed.with(
Serdes.String(), /* key serde */
Serdes.Long() /* value serde */
)
);KTable: Changelog stream (latest value per key)
import org.apache.kafka.streams.StreamsBuilder;
StreamsBuilder builder = new StreamsBuilder();
builder.table("input-topic");GlobalKTable: Fully replicated table available to all instances
KTable: Each application instance gets data from only 1 partition.
GlobalKTable: Each application instance gets data from all partitions.Writing Streams to Kafka
KStream<String, Long> stream = ...;
// Write the stream to the output topic, using the configured default key
// and value serdes.
stream.to("my-stream-output-topic");
// Write the stream to the output topic, using explicit key and value serdes,
// (thus overriding the defaults in the config properties).
stream.to("my-stream-output-topic", Produced.with(Serdes.String(), Serdes.Long()));Any streams and tables may be (continuously) written back to a Kafka topic. The output data might be re-partitioned depending on the situation.
Repartitioning
Manual repartitioning with specified partition count:
KStream<byte[], String> stream = ... ;
KStream<byte[], String> repartitionedStream = stream.repartition(Repartitioned.numberOfPartitions(10));Kafka Streams manages the generated topic as an internal topic, ensuring data purging and allowing for scaling downstream sub-topologies. This operation is useful when key-changing operations are performed beforehand and auto-repartitioning is not triggered.
Joins and Co-partitioning
Join Co-partitioning Requirements:
For equi-joins in Kafka Streams, input data must be co-partitioned. This ensures that records with the same key from both sides of the join are delivered to the same stream task.
Requirements for data co-partitioning:
1. Input topics (left and right sides) must have the same number of partitions.
2. All applications writing to the input topics must use the same partitioning strategy to ensure records with the same key are delivered to the same partition number.
- This applies to producer settings like `partitioner.class` (e.g., `ProducerConfig.PARTITIONER_CLASS_CONFIG`) and Kafka Streams `StreamPartitioner` for operations like `KStream#to()`.
- Using default partitioner settings across all applications generally satisfies this requirement.
Why co-partitioning is required:
- KStream-KStream, KTable-KTable, and KStream-KTable joins are performed based on record keys (e.g., `leftRecord.key == rightRecord.key`). Co-partitioning by key ensures these records meet.
Exceptions where co-partitioning is NOT required:
1. KStream-GlobalKTable joins:
- All partitions of the GlobalKTable's underlying changelog stream are available to each KafkaStreams instance.
- A `KeyValueMapper` allows non-key based joins from KStream to GlobalKTable.
2. KTable-KTable Foreign-Key joins:
- Kafka Streams internally ensures co-partitioning for these joins.Stateful Operations
Kafka Streams supports stateful operations like aggregations, windowing, and joins using state stores.
State Store Types:
- Key-Value Stores: For aggregations and joins
- Window Stores: For time-based operations
- Session Stores: For session-based aggregations
State Store Configuration:
Internal Topic Configuration:
- message.timestamp.type: 'CreateTime' for all internal topics.
- Internal Repartition Topics:
- compaction.policy: 'delete'
- retention.time: -1 (infinite)
- Internal Changelog Topics for Key-Value Stores:
- compaction.policy: 'compact'
- Internal Changelog Topics for Windowed Key-Value Stores:
- compaction.policy: 'delete,compact'
- retention.time: 24 hours + windowed store setting
- Internal Changelog Topics for Versioned State Stores:
- cleanup.policy: 'compact'
- min.compaction.lag.ms: 24 hours + store's historyRetentionMsApplication Parallelism
The parallelism of a Kafka Streams application is primarily determined by how many partitions the input topics have. For example, if your application reads from a single topic that has ten partitions, then you can run up to ten instances of your applications. You can run further instances, but these will be idle.
The number of topic partitions is the upper limit for the parallelism of your Kafka Streams application and for the number of running instances of your application.
To achieve balanced workload processing across application instances and to prevent processing hotpots, you should distribute data and processing workloads:
Data should be equally distributed across topic partitions. For example, if two topic partitions each have 1 million messages, this is better than a single partition with 2 million messages and none in the other.
Processing workload should be equally distributed across topic partitions. For example, if the time to process messages varies widely, then it is better to spread the processing-intensive messages across partitions rather than storing these messages within the same partition.Kafka Streams Configuration
Client Prefixes:
Properties streamsSettings = new Properties();
// same value for consumer, producer, and admin client
streamsSettings.put("PARAMETER_NAME", "value");
// different values for consumer and producer
streamsSettings.put("consumer.PARAMETER_NAME", "consumer-value");
streamsSettings.put("producer.PARAMETER_NAME", "producer-value");
streamsSettings.put("admin.PARAMETER_NAME", "admin-value");
// alternatively, you can use
streamsSettings.put(StreamsConfig.consumerPrefix("PARAMETER_NAME"), "consumer-value");
streamsSettings.put(StreamsConfig.producerPrefix("PARAMETER_NAME"), "producer-value");
streamsSettings.put(StreamsConfig.adminClientPrefix("PARAMETER_NAME"), "admin-value");Specific Consumer Types:
Properties streamsSettings = new Properties();
// same config value for all consumer types
streamsSettings.put("consumer.PARAMETER_NAME", "general-consumer-value");
// set a different restore consumer config. This would make restore consumer take restore-consumer-value,
// while main consumer and global consumer stay with general-consumer-value
streamsSettings.put("restore.consumer.PARAMETER_NAME", "restore-consumer-value");
// alternatively, you can use
streamsSettings.put(StreamsConfig.restoreConsumerPrefix("PARAMETER_NAME"), "restore-consumer-value");Topic Configuration:
Properties streamsSettings = new Properties();
// Override default for both changelog and repartition topics
streamsSettings.put("topic.PARAMETER_NAME", "topic-value");
// alternatively, you can use
streamsSettings.put(StreamsConfig.topicPrefix("PARAMETER_NAME"), "topic-value");Exactly-Once Semantics in Streams
Producer Client ID Naming Schema:
- at-least-once (default):
`[client.Id]-StreamThread-[sequence-number]`
- exactly-once (EOS version 1):
`[client.Id]-StreamThread-[sequence-number]-[taskId]`
- exactly-once-beta (EOS version 2):
`[client.Id]-StreamThread-[sequence-number]`
Where `[client.Id]` is either set via Streams configuration parameter `client.id` or defaults to `[application.id]-[processId]` (`[processId]` is a random UUID).EOS Configuration:
Parameter Name: isolation.level
Corresponding Client: Consumer
Streams Default: READ_COMMITTED
Parameter Name: enable.idempotence
Corresponding Client: Producer
Streams Default: trueParameter Name: transaction.timeout.ms
Corresponding Client: Producer
Streams Default: 10000
Parameter Name: delivery.timeout.ms
Corresponding Client: Producer
Streams Default: Integer.MAX_VALUETopology Naming and Stability
Default Topology (Auto-generated names):
Topologies: Sub-topology: 0
Source: KSTREAM-SOURCE-0000000000 (topics: [input]) --> KSTREAM-FILTER-0000000001
Processor: KSTREAM-FILTER-0000000001 (stores: []) --> KSTREAM-MAPVALUES-0000000002
<-- KSTREAM-SOURCE-0000000000
Processor: KSTREAM-MAPVALUES-0000000002 (stores: []) --> KSTREAM-SINK-0000000003
<-- KSTREAM-FILTER-0000000001
Sink: KSTREAM-SINK-0000000003 (topic: output)
<-- KSTREAM-MAPVALUES-0000000002Explicit Naming for Stability:
Kafka Streams Topology Naming:
- Aggregation repartition topics: Grouped
- KStream-KTable Join repartition topic: Joined
- KStream-KStream Join repartition topics: StreamJoined
- KStream-KTable Join state stores: Joined
- KStream-KStream Join state stores: StreamJoined
- State Stores (for aggregations and KTable-KTable joins): Materialized
- Stream/Table non-stateful operations: NamedOperation Naming Class
------------------------------------------------------------------
Aggregation repartition topics Grouped
KStream-KStream Join repartition topics StreamJoined
KStream-KTable Join repartition topic Joined
KStream-KStream Join state stores StreamJoined
State Stores (for aggregations and KTable-KTable joins) Materialized
Stream/Table non-stateful operations NamedEnforce Explicit Naming:
Properties props = new Properties();
props.put(StreamsConfig.ENSURE_EXPLICIT_INTERNAL_RESOURCE_NAMING_CONFIG, true);This prevents the application from starting with auto-generated names, guaranteeing stability across topology updates.
Topology Optimization
"topology.optimization":"all""topology.optimization":"none"Topology optimization allows reuse of source topics as changelog topics, crucial when migrating from KStreamBuilder to StreamsBuilder.
WordCount Example with Topology
$ mvn clean package
$ mvn exec:java -Dexec.mainClass=myapps.WordCount
Sub-topologies:
Sub-topology: 0
Source: KSTREAM-SOURCE-0000000000(topics: streams-plaintext-input) --> KSTREAM-FLATMAPVALUES-0000000001
Processor: KSTREAM-FLATMAPVALUES-0000000001(stores: \[\]) --> KSTREAM-KEY-SELECT-0000000002 <-- KSTREAM-SOURCE-0000000000
Processor: KSTREAM-KEY-SELECT-0000000002(stores: \[\]) --> KSTREAM-FILTER-0000000005 <-- KSTREAM-FLATMAPVALUES-0000000001
Processor: KSTREAM-FILTER-0000000005(stores: \[\]) --> KSTREAM-SINK-0000000004 <-- KSTREAM-KEY-SELECT-0000000002
Sink: KSTREAM-SINK-0000000004(topic: counts-store-repartition) <-- KSTREAM-FILTER-0000000005
Sub-topology: 1
Source: KSTREAM-SOURCE-0000000006(topics: counts-store-repartition) --> KSTREAM-AGGREGATE-0000000003
Processor: KSTREAM-AGGREGATE-0000000003(stores: \[counts-store\]) --> KTABLE-TOSTREAM-0000000007 <-- KSTREAM-SOURCE-0000000006
Processor: KTABLE-TOSTREAM-0000000007(stores: \[\]) --> KSTREAM-SINK-0000000008 <-- KSTREAM-AGGREGATE-0000000003
Sink: KSTREAM-SINK-0000000008(topic: streams-wordcount-output) <-- KTABLE-TOSTREAM-0000000007
Global Stores: noneThis topology shows two disconnected sub-topologies, their sources, processors, sinks, and the repartition topic (counts-store-repartition) used for shuffling data by aggregation key.
Schema Registry
Schema registries enforce data contracts between producers and consumers, ensuring data integrity and preventing malformed events.
Data Contracts with Schema Registry:
- Purpose: Ensure events written to Kafka can be read properly and prevent malformed events.
- Implementation: Deploy a schema registry alongside the Kafka cluster.
- Functionality: Manages event schemas and maps them to topics, guiding producers on correct event formats.
- Note: Kafka does not include a schema registry; third-party implementations are available.Schema Registry Benefits
1. Schema Evolution: Manage schema changes over time 2. Compatibility Checking: Enforce backward/forward compatibility 3. Centralized Management: Single source of truth for schemas 4. Type Safety: Compile-time type checking 5. Documentation: Auto-generated schema documentation 6. Versioning: Track schema versions per subject
Schema Formats
Supported Formats:
- Avro: Compact binary format with rich schema evolution
- JSON Schema: Human-readable with schema validation
- Protobuf: Google's Protocol Buffers
Schema Evolution Compatibility Modes
1. BACKWARD: New schema can read old data 2. FORWARD: Old schema can read new data 3. FULL: Both backward and forward compatible 4. NONE: No compatibility checking
Avro Producer Example
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.producer.*;
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
props.put("schema.registry.url", "http://localhost:8081");
String userSchema = "{"
+ "\"type\":\"record\","
+ "\"name\":\"User\","
+ "\"fields\":["
+ " {\"name\":\"name\",\"type\":\"string\"},"
+ " {\"name\":\"age\",\"type\":\"int\"}"
+ "]}";
Schema.Parser parser = new Schema.Parser();
Schema schema = parser.parse(userSchema);
GenericRecord user = new GenericData.Record(schema);
user.put("name", "John Doe");
user.put("age", 30);
ProducerRecord<String, GenericRecord> record =
new ProducerRecord<>("users", "user1", user);
producer.send(record);Kafka Connect
Kafka Connect is a framework for streaming data between Kafka and external systems.
Kafka Connect Sink Connector Input Topics
Configuration options for sink connectors to specify input topics using a comma-separated list or a regular expression.
topics
topics.regexConnector Types
Source Connectors: Import data into Kafka
- Database CDC (Debezium)
- File systems
- Message queues
- Cloud services (S3, BigQuery)
- APIs and webhooks
Sink Connectors: Export data from Kafka
- Databases (JDBC, Elasticsearch)
- Data warehouses
- Object storage
- Search engines
- Analytics platforms
Connector Configuration
Source Connector Example (JDBC):
{
"name": "jdbc-source-connector",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
"tasks.max": "1",
"connection.url": "jdbc:postgresql://localhost:5432/mydb",
"connection.user": "postgres",
"connection.password": "password",
"table.whitelist": "users,orders",
"mode": "incrementing",
"incrementing.column.name": "id",
"topic.prefix": "postgres-"
}
}Sink Connector Example (Elasticsearch):
{
"name": "elasticsearch-sink-connector",
"config": {
"connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
"tasks.max": "1",
"topics": "user-events,order-events",
"connection.url": "http://localhost:9200",
"type.name": "_doc",
"key.ignore": "false"
}
}Change Data Capture (CDC)
CDC captures database changes and streams them to Kafka in real-time.
Benefits:
- Real-time data synchronization
- Event sourcing from existing databases
- Microservices data integration
- Zero-downtime migrations
Popular CDC Connectors:
- Debezium (MySQL, PostgreSQL, MongoDB, SQL Server)
- Oracle GoldenGate
- Maxwell's Daemon
Topic Management
Kafka Streams Topic Management:
User Topics:
- Input Topics: Specified via source processors (e.g., StreamsBuilder#stream(), StreamsBuilder#table(), Topology#addSource()).
- Output Topics: Specified via sink processors (e.g., KStream#to(), KTable.to(), Topology#addSink()).
- Management: Must be created and managed manually ahead of time (e.g., via topic tools).
- Sharing: If shared, users must coordinate topic management.
- Auto-creation: Discouraged due to potential cluster configuration and default topic settings (e.g., replication factor).
Internal Topics:
- Purpose: Used internally by the application for state stores (e.g., changelog topics).
- Creation: Created by the application itself.
- Usage: Only used by the specific stream application.
- Permissions: Requires underlying clients to have admin permissions on Kafka brokers if security is enabled.
- Naming Convention: Typically follows '<application.id>-<operatorName>-<suffix>', but not guaranteed for future releases.Topic Operations
DESCRIBE_PRODUCERS:
- Action: Read
- Resource: Topic
DESCRIBE_TOPIC_PARTITIONS:
- Action: Describe
- Resource: TopicProduce Test Messages
$ bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic streams-plaintext-input
>all streams lead to kafka
>hello kafka streams
>join kafka summitMonitoring and Metrics
Common Metrics
Metric Name: outgoing-byte-rate
Description: The average number of outgoing bytes sent per second for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: outgoing-byte-total
Description: The total number of outgoing bytes sent for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: request-rate
Description: The average number of requests sent per second for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: request-total
Description: The total number of requests sent for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: request-size-avg
Description: The average size of all requests in the window for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: request-size-max
Description: The maximum size of any request sent in the window for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)
Metric Name: incoming-byte-rate
Description: The average number of incoming bytes received per second for a node.
Mbean Name Pattern: kafka.[producer|consumer|connect]:type=[consumer|producer|connect]-node-metrics,client-id=([-.w]+),node-id=([0-9]+)Key Monitoring Areas
1. Producer Metrics:
- record-send-rate
- record-error-rate
- compression-rate-avg
- buffer-available-bytes
2. Consumer Metrics:
- records-consumed-rate
- fetch-latency-avg
- records-lag-max
- commit-latency-avg
3. Broker Metrics:
- UnderReplicatedPartitions
- OfflinePartitionsCount
- ActiveControllerCount
- RequestHandlerAvgIdlePercent
4. Streams Metrics:
- process-rate
- process-latency-avg
- commit-rate
- poll-rate
Production Deployment
Cluster Architecture
Multi-Broker Setup:
1. Brokers: Typically 3+ brokers for fault tolerance 2. Replication: Replication factor 3 for production 3. Partitions: More partitions = more parallelism 4. ZooKeeper/KRaft: 3 or 5 nodes for quorum
High Availability Configuration
Broker Configuration:
# Broker ID
broker.id=1
# Listeners
listeners=PLAINTEXT://broker1:9092,SSL://broker1:9093
# Log directories (use multiple disks)
log.dirs=/data/kafka-logs-1,/data/kafka-logs-2
# Replication
default.replication.factor=3
min.insync.replicas=2
# Leader election
unclean.leader.election.enable=false
auto.leader.rebalance.enable=true
# Log retention
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000Eligible Leader Replicas (ELR)
API: DescribeTopicPartitions
Purpose: Fetches detailed information about topic partitions, including Eligible Leader Replicas (ELR).
Usage:
- Via Admin Client: The admin client can fetch ELR info by describing topics.
- Direct API Call: Use the DescribeTopicPartitions API endpoint.
ELR Selection Logic:
- If ELR is not empty, select a replica that is not fenced.
- Select the last known leader if it is unfenced, mimicking pre-4.0 behavior when all replicas are offline.
Dependencies/Side Effects:
- Updating `min.insync.replicas` for a topic will clean the ELR field for that topic.
- Updating the cluster default `min.insync.replicas` will clean ELR fields for all topics.
Return Values:
- ELR status and related replica information for partitions.Security Configuration
SSL/TLS Encryption:
# SSL configuration
listeners=SSL://broker:9093
security.inter.broker.protocol=SSL
ssl.keystore.location=/var/private/ssl/kafka.server.keystore.jks
ssl.keystore.password=password
ssl.key.password=password
ssl.truststore.location=/var/private/ssl/kafka.server.truststore.jks
ssl.truststore.password=password
ssl.client.auth=requiredSASL Authentication:
# SASL/PLAIN configuration
listeners=SASL_SSL://broker:9093
security.inter.broker.protocol=SASL_SSL
sasl.mechanism.inter.broker.protocol=PLAIN
sasl.enabled.mechanisms=PLAIN
# JAAS configuration
listener.name.sasl_ssl.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
username="admin" \
password="admin-secret" \
user_admin="admin-secret" \
user_alice="alice-secret";Performance Tuning
Broker Tuning:
# Network threads
num.network.threads=8
# I/O threads
num.io.threads=16
# Socket buffer sizes
socket.send.buffer.bytes=1048576
socket.receive.buffer.bytes=1048576
socket.request.max.bytes=104857600
# Replication
num.replica.fetchers=4
replica.fetch.max.bytes=1048576
# Log flush (rely on OS page cache)
log.flush.interval.messages=9223372036854775807
log.flush.interval.ms=nullProducer Tuning for Throughput:
acks=1
linger.ms=100
batch.size=65536
compression.type=lz4
buffer.memory=67108864
max.in.flight.requests.per.connection=5Consumer Tuning:
fetch.min.bytes=1
fetch.max.wait.ms=500
max.partition.fetch.bytes=1048576
max.poll.records=500
session.timeout.ms=30000
heartbeat.interval.ms=3000Best Practices
Producer Best Practices
1. Enable Idempotence: Prevent duplicate messages 2. Configure Acks Properly: Balance durability and throughput 3. Use Compression: Reduce network and storage costs 4. Batch Messages: Configure linger.ms and batch.size 5. Handle Retries: Implement proper retry logic 6. Monitor Metrics: Track send rates and error rates 7. Partition Strategy: Use meaningful keys for ordering 8. Close Gracefully: Call close() with timeout
Consumer Best Practices
1. Manual Offset Management: For at-least-once semantics 2. Handle Rebalancing: Implement ConsumerRebalanceListener 3. Minimize Poll Duration: Process efficiently 4. Monitor Consumer Lag: Alert on high lag 5. Thread Safety: One consumer per thread 6. Graceful Shutdown: Close consumers properly 7. Error Handling: Retry transient failures, DLQ for permanent 8. Seek Capability: Use seek() for replay scenarios
Kafka Streams Best Practices
1. Explicit Naming: Use Named, Grouped, Materialized for stability 2. State Store Management: Configure changelog topics properly 3. Error Handling: Implement ProductionExceptionHandler 4. Scaling: Match application instances to input partitions 5. Testing: Use TopologyTestDriver for unit tests 6. Monitoring: Track lag, processing rate, error rate 7. Exactly-Once: Enable for critical applications 8. Graceful Shutdown: Handle signals properly
Topic Design Best Practices
1. Partition Count: Based on throughput requirements 2. Replication Factor: 3 for production topics 3. Retention: Set based on use case (time or size) 4. Compaction: Use for changelog and lookup topics 5. Naming Convention: Consistent naming scheme 6. Documentation: Document topic purpose and schema 7. Access Control: Implement proper ACLs 8. Monitoring: Track partition metrics
Operational Best Practices
1. Monitoring: Comprehensive metrics collection 2. Alerting: Alert on critical metrics 3. Capacity Planning: Monitor disk, network, CPU 4. Backup: Implement disaster recovery strategy 5. Upgrades: Rolling upgrades with testing 6. Security: Enable encryption and authentication 7. Documentation: Maintain runbooks 8. Testing: Load test before production
Common Patterns
Pattern 1: Event Sourcing
Store all state changes as immutable events:
// Order events
OrderCreated -> OrderPaid -> OrderShipped -> OrderDelivered
// Event store as Kafka topic
Topic: order-events
Compaction: None (keep full history)
Retention: Infinite or very longPattern 2: CQRS (Command Query Responsibility Segregation)
Separate read and write models:
// Write side: Commands produce events
commands -> producers -> events-topic
// Read side: Consumers build projections
events-topic -> streams -> materialized-view (KTable)Pattern 3: Saga Pattern
Distributed transaction coordination:
// Order saga
order-requested -> payment-requested -> payment-completed ->
inventory-reserved -> order-confirmed
// Compensating transactions on failure
payment-failed -> order-cancelledPattern 4: Outbox Pattern
Reliably publish database changes:
// Database transaction writes to outbox table
BEGIN TRANSACTION;
INSERT INTO orders VALUES (...);
INSERT INTO outbox VALUES (event_data);
COMMIT;
// CDC connector reads outbox and publishes to Kafka
Debezium -> outbox-topic -> downstream consumersPattern 5: Fan-out Pattern
Broadcast events to multiple consumers:
// Single topic, multiple consumer groups
user-events topic
-> email-service (consumer group: email)
-> analytics-service (consumer group: analytics)
-> notification-service (consumer group: notifications)Pattern 6: Dead Letter Queue (DLQ)
Handle processing failures:
try {
processRecord(record);
} catch (RetriableException e) {
// Retry
retry(record);
} catch (NonRetriableException e) {
// Send to DLQ
sendToDLQ(record, e);
}Pattern 7: Windowed Aggregations
Time-based aggregations:
KStream<String, PageView> views = ...;
// Tumbling window: non-overlapping fixed windows
views.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.count();
// Hopping window: overlapping windows
views.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5))
.advanceBy(Duration.ofMinutes(1)))
.count();
// Session window: activity-based windows
views.groupByKey()
.windowedBy(SessionWindows.with(Duration.ofMinutes(30)))
.count();Troubleshooting
Common Issues
Issue: Consumer lag increasing
- Check consumer processing time
- Scale consumer group (add instances)
- Optimize processing logic
- Increase max.poll.records if appropriate
Issue: Messages not arriving
- Check producer send() error callbacks
- Verify topic exists and is accessible
- Check network connectivity
- Review broker logs for errors
Issue: Duplicate messages
- Enable idempotent producer
- Implement idempotent consumer processing
- Check offset commit strategy
- Verify exactly-once configuration
Issue: Rebalancing taking too long
- Reduce max.poll.interval.ms
- Increase session.timeout.ms
- Optimize poll() processing time
- Check consumer health
Issue: Partition leader unavailable
- Check broker health and logs
- Verify replication status
- Check network between brokers
- Review ISR (In-Sync Replicas)
Issue: Out of memory errors
- Reduce batch.size and buffer.memory
- Tune JVM heap settings
- Monitor memory usage
- Check for memory leaks in processing
Migration and Upgrade Strategies
Kafka Streams Migration
KStreamBuilder to StreamsBuilder:
kstream.repartition(...);
// or for user-managed topics:
kstream.to("user-topic");
streamsBuilder.stream("user-topic");Replaces KStream.through() for managing topic repartitioning.
Topic Prefix Configuration:
Properties props = new Properties();
props.put(StreamsConfig.topicPrefix("my-prefix.") + "replication.factor", "3");
KafkaStreams streams = new KafkaStreams(topology, props);Rolling Upgrades
1. Prepare: Test new version in staging 2. Upgrade Brokers: One broker at a time 3. Verify: Check cluster health after each broker 4. Upgrade Clients: Producers, consumers, streams apps 5. Monitor: Watch metrics throughout process
Resources and References
- Apache Kafka Documentation: https://kafka.apache.org/documentation/
- Confluent Platform: https://docs.confluent.io/
- Kafka Streams Documentation: https://kafka.apache.org/documentation/streams/
- Schema Registry: https://docs.confluent.io/platform/current/schema-registry/
- Kafka Connect: https://kafka.apache.org/documentation/#connect
---
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Stream Processing, Event-Driven Architecture, Real-Time Data Compatible With: Apache Kafka 2.x, 3.x, Confluent Platform
Kafka Stream Processing Examples
Comprehensive examples demonstrating Kafka producers, consumers, Kafka Streams, connectors, and production patterns.
Table of Contents
1. Producer Examples 2. Consumer Examples 3. Kafka Streams Examples 4. Schema Registry Examples 5. Kafka Connect Examples 6. Production Patterns 7. Testing Strategies
Producer Examples
Example 1: Simple Synchronous Producer
Basic producer with synchronous sends and error handling.
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class SimpleSyncProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record =
new ProducerRecord<>("events", "key-" + i, "message-" + i);
try {
// Synchronous send - blocks until acknowledged
RecordMetadata metadata = producer.send(record).get();
System.out.printf("Sent record(key=%s value=%s) " +
"meta(partition=%d, offset=%d)%n",
record.key(), record.value(),
metadata.partition(), metadata.offset());
} catch (ExecutionException | InterruptedException e) {
System.err.println("Error sending record: " + e.getMessage());
}
}
}
}
}When to Use:
- Small number of messages
- Need confirmation before proceeding
- Testing and debugging
Example 2: Asynchronous Producer with Callbacks
High-throughput producer with async sends and callback handling.
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
public class AsyncProducerWithCallbacks {
public static void main(String[] args) throws InterruptedException {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 32768);
CountDownLatch latch = new CountDownLatch(1000);
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
for (int i = 0; i < 1000; i++) {
ProducerRecord<String, String> record =
new ProducerRecord<>("high-throughput-topic", "key-" + i, "data-" + i);
// Async send with callback
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception != null) {
System.err.println("Error producing record: " + exception.getMessage());
} else {
System.out.printf("Produced: partition=%d offset=%d%n",
metadata.partition(), metadata.offset());
}
latch.countDown();
}
});
}
// Wait for all callbacks to complete
latch.await();
}
}
}Benefits:
- High throughput via batching
- Non-blocking sends
- Error handling per message
Example 3: Idempotent Producer (Exactly-Once)
Producer configured for exactly-once semantics to prevent duplicates.
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class IdempotentProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// Exactly-once producer settings
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5);
// Optional: Transactional ID for transactions
props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "my-transactional-id");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
// Initialize transactions
producer.initTransactions();
try {
// Begin transaction
producer.beginTransaction();
// Send multiple records atomically
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record =
new ProducerRecord<>("transactions", "txn-" + i, "data-" + i);
producer.send(record);
}
// Commit transaction
producer.commitTransaction();
System.out.println("Transaction committed successfully");
} catch (Exception e) {
// Abort transaction on error
producer.abortTransaction();
System.err.println("Transaction aborted: " + e.getMessage());
}
}
}
}Key Features:
- No duplicate messages even with retries
- Atomic writes across partitions
- Exactly-once delivery guarantee
Example 4: Custom Partitioner
Implement custom partitioning logic for specialized routing.
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.*;
// Custom partitioner routes VIP users to partition 0
class VIPPartitioner implements Partitioner {
private Set<String> vipUsers = new HashSet<>(Arrays.asList("user1", "user2", "user3"));
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
int partitionCount = cluster.partitionCountForTopic(topic);
if (key != null && vipUsers.contains(key.toString())) {
return 0; // VIP partition
}
// Default partitioning for others
return Math.abs(key.hashCode()) % (partitionCount - 1) + 1;
}
@Override
public void close() {}
@Override
public void configure(Map<String, ?> configs) {}
}
public class CustomPartitionerProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, VIPPartitioner.class.getName());
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
String[] users = {"user1", "user4", "user2", "user5", "user3"};
for (String user : users) {
ProducerRecord<String, String> record =
new ProducerRecord<>("user-events", user, "event-data");
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.printf("User %s -> Partition %d%n",
user, metadata.partition());
}
});
}
}
}
}Use Cases:
- Priority routing
- Geographic partitioning
- Load balancing strategies
Example 5: Producer with Compression
Configure compression to reduce network and storage overhead.
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CompressedProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// Enable compression (options: none, gzip, snappy, lz4, zstd)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");
// Increase batch size to maximize compression benefits
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 65536);
props.put(ProducerConfig.LINGER_MS_CONFIG, 100);
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
// Send large messages that benefit from compression
String largeMessage = generateLargeMessage(10000);
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record =
new ProducerRecord<>("large-messages", "key-" + i, largeMessage);
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.printf("Compressed message sent: offset=%d%n", metadata.offset());
}
});
}
}
}
private static String generateLargeMessage(int size) {
StringBuilder sb = new StringBuilder(size);
for (int i = 0; i < size; i++) {
sb.append("Lorem ipsum dolor sit amet ");
}
return sb.toString();
}
}Compression Comparison:
- lz4: Fast compression/decompression, good compression ratio
- snappy: Very fast, moderate compression
- gzip: Best compression ratio, slower
- zstd: Excellent balance (Kafka 2.1+)
Consumer Examples
Example 6: Basic Consumer with Manual Commit
Consumer with manual offset management for at-least-once semantics.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class ManualCommitConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "manual-commit-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(Collections.singletonList("events"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Consumed: partition=%d offset=%d key=%s value=%s%n",
record.partition(), record.offset(), record.key(), record.value());
// Process record
processRecord(record);
}
// Commit offsets after processing all records in batch
try {
consumer.commitSync();
} catch (CommitFailedException e) {
System.err.println("Commit failed: " + e.getMessage());
}
}
}
}
private static void processRecord(ConsumerRecord<String, String> record) {
// Business logic here
// If processing fails, exception prevents commit
}
}Guarantees:
- At-least-once delivery
- No message loss if processing fails
- Potential duplicates on rebalance
Example 7: Consumer with Rebalance Listener
Handle partition rebalancing with state cleanup and offset management.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class RebalanceListenerConsumer {
private static Map<TopicPartition, Long> currentOffsets = new HashMap<>();
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "rebalance-aware-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(
Collections.singletonList("events"),
new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
System.out.println("Partitions revoked: " + partitions);
// Commit current offsets before losing partitions
consumer.commitSync(currentOffsets);
currentOffsets.clear();
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
System.out.println("Partitions assigned: " + partitions);
// Seek to specific offset if needed
for (TopicPartition partition : partitions) {
System.out.printf("Starting position for %s: %d%n",
partition, consumer.position(partition));
}
}
}
);
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
processRecord(record);
// Track current offset for each partition
currentOffsets.put(
new TopicPartition(record.topic(), record.partition()),
record.offset() + 1
);
}
// Async commit with callback
consumer.commitAsync((offsets, exception) -> {
if (exception != null) {
System.err.println("Async commit failed: " + exception.getMessage());
}
});
}
}
}
private static void processRecord(ConsumerRecord<String, String> record) {
System.out.printf("Processing: %s%n", record.value());
}
}Features:
- Clean shutdown before rebalance
- State management per partition
- Async commits for performance
Example 8: Consumer Seek and Replay
Implement replay functionality by seeking to specific offsets.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class SeekAndReplayConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "seek-replay-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(Collections.singletonList("events"));
// Initial poll to join group and get partition assignment
consumer.poll(Duration.ofMillis(0));
// Seek to beginning of all assigned partitions
Set<TopicPartition> assignedPartitions = consumer.assignment();
consumer.seekToBeginning(assignedPartitions);
System.out.println("Replaying from beginning...");
// Or seek to specific offset
// for (TopicPartition partition : assignedPartitions) {
// consumer.seek(partition, 100); // Start from offset 100
// }
// Or seek to timestamp (messages after specific time)
// Map<TopicPartition, Long> timestampsToSearch = new HashMap<>();
// long timestamp = System.currentTimeMillis() - (24 * 60 * 60 * 1000); // 24h ago
// for (TopicPartition partition : assignedPartitions) {
// timestampsToSearch.put(partition, timestamp);
// }
// Map<TopicPartition, OffsetAndTimestamp> offsets =
// consumer.offsetsForTimes(timestampsToSearch);
// for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : offsets.entrySet()) {
// if (entry.getValue() != null) {
// consumer.seek(entry.getKey(), entry.getValue().offset());
// }
// }
int messageCount = 0;
while (messageCount < 1000) { // Replay 1000 messages
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Replayed: offset=%d key=%s value=%s%n",
record.offset(), record.key(), record.value());
messageCount++;
}
}
System.out.println("Replay completed");
}
}
}Use Cases:
- Reprocessing historical data
- Recovery from processing errors
- Testing with production data
Example 9: Multi-Topic Consumer with Pattern
Subscribe to multiple topics using pattern matching.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
public class MultiTopicPatternConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "multi-topic-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
// Subscribe to all topics matching pattern (e.g., user-events-*, order-events-*)
Pattern pattern = Pattern.compile(".*-events-.*");
consumer.subscribe(pattern, new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
System.out.println("Partitions revoked: " + partitions);
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
System.out.println("Partitions assigned: " + partitions);
// Print topics being consumed
Set<String> topics = new HashSet<>();
for (TopicPartition partition : partitions) {
topics.add(partition.topic());
}
System.out.println("Consuming from topics: " + topics);
}
});
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Topic=%s Partition=%d Offset=%d Key=%s Value=%s%n",
record.topic(), record.partition(), record.offset(),
record.key(), record.value());
// Route processing based on topic
if (record.topic().startsWith("user-events")) {
processUserEvent(record);
} else if (record.topic().startsWith("order-events")) {
processOrderEvent(record);
}
}
}
}
}
private static void processUserEvent(ConsumerRecord<String, String> record) {
System.out.println("Processing user event: " + record.value());
}
private static void processOrderEvent(ConsumerRecord<String, String> record) {
System.out.println("Processing order event: " + record.value());
}
}Benefits:
- Dynamic topic subscription
- Automatic inclusion of new matching topics
- Centralized processing logic
Kafka Streams Examples
Example 10: WordCount with Kafka Streams
Classic streaming example with stateful aggregation.
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.util.Arrays;
import java.util.Properties;
public class WordCountStreams {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
StreamsBuilder builder = new StreamsBuilder();
// Read from input topic
KStream<String, String> textLines = builder.stream("text-input");
// WordCount processing
KTable<String, Long> wordCounts = textLines
// Split text into words
.flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+")))
// Group by word
.groupBy((key, word) -> word)
// Count occurrences
.count(Materialized.as("counts-store"));
// Write results to output topic
wordCounts.toStream()
.to("word-counts-output", Produced.with(Serdes.String(), Serdes.Long()));
KafkaStreams streams = new KafkaStreams(builder.build(), props);
// Add shutdown hook for graceful termination
Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
streams.start();
}
}Example 11: Stream-Stream Join
Join two streams based on time windows.
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.time.Duration;
import java.util.Properties;
public class StreamStreamJoin {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "stream-join-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
StreamsBuilder builder = new StreamsBuilder();
// Page view events
KStream<String, String> pageViews = builder.stream("page-views");
// Click events
KStream<String, String> clicks = builder.stream("clicks");
// Join within 5-minute window
KStream<String, String> joined = pageViews.join(
clicks,
(pageView, click) -> "PageView: " + pageView + ", Click: " + click,
JoinWindows.of(Duration.ofMinutes(5)),
StreamJoined.with(Serdes.String(), Serdes.String(), Serdes.String())
);
joined.to("page-view-clicks");
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
}
}Example 12: Windowed Aggregations
Time-based aggregations with tumbling, hopping, and session windows.
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.time.Duration;
import java.util.Properties;
public class WindowedAggregations {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "windowed-agg-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Long> events = builder.stream(
"sensor-events",
Consumed.with(Serdes.String(), Serdes.Long())
);
// Tumbling Window: Fixed, non-overlapping 5-minute windows
KTable<Windowed<String>, Long> tumblingCounts = events
.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.count();
tumblingCounts.toStream()
.map((windowedKey, count) -> {
String key = windowedKey.key() + "@" +
windowedKey.window().startTime() + "-" +
windowedKey.window().endTime();
return new KeyValue<>(key, count);
})
.to("tumbling-counts", Produced.with(Serdes.String(), Serdes.Long()));
// Hopping Window: Overlapping 5-minute windows advancing by 1 minute
KTable<Windowed<String>, Long> hoppingCounts = events
.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5))
.advanceBy(Duration.ofMinutes(1)))
.count();
hoppingCounts.toStream()
.map((windowedKey, count) -> {
String key = windowedKey.key() + "@" +
windowedKey.window().startTime() + "-" +
windowedKey.window().endTime();
return new KeyValue<>(key, count);
})
.to("hopping-counts", Produced.with(Serdes.String(), Serdes.Long()));
// Session Window: Activity-based windows with 30-minute inactivity gap
KTable<Windowed<String>, Long> sessionCounts = events
.groupByKey()
.windowedBy(SessionWindows.with(Duration.ofMinutes(30)))
.count();
sessionCounts.toStream()
.map((windowedKey, count) -> {
String key = windowedKey.key() + "@session-" +
windowedKey.window().startTime() + "-" +
windowedKey.window().endTime();
return new KeyValue<>(key, count);
})
.to("session-counts", Produced.with(Serdes.String(), Serdes.Long()));
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
}
}Example 13: Stream-Table Join with GlobalKTable
Join stream with reference data from GlobalKTable.
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.util.Properties;
public class StreamTableJoin {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "stream-table-join-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
StreamsBuilder builder = new StreamsBuilder();
// Stream of user activity events
KStream<String, String> userActivity = builder.stream(
"user-activity",
Consumed.with(Serdes.String(), Serdes.String())
);
// GlobalKTable of user profiles (fully replicated to all instances)
GlobalKTable<String, String> userProfiles = builder.globalTable(
"user-profiles",
Consumed.with(Serdes.String(), Serdes.String())
);
// Join activity with profile data
KStream<String, String> enrichedActivity = userActivity.join(
userProfiles,
(activityKey, activityValue) -> activityKey, // Key extractor
(activity, profile) -> "Activity: " + activity + ", Profile: " + profile
);
enrichedActivity.to("enriched-activity");
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
}
}Example 14: Exactly-Once Streams Processing
Configure Kafka Streams for exactly-once semantics.
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.util.Properties;
public class ExactlyOnceStreams {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "exactly-once-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// Enable exactly-once processing (EOS version 2)
props.put(StreamsConfig.PROCESSING_GUARANTEE_CONFIG, StreamsConfig.EXACTLY_ONCE_V2);
// Configure for exactly-once
props.put(StreamsConfig.REPLICATION_FACTOR_CONFIG, 3);
props.put(StreamsConfig.producerPrefix(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG), true);
props.put(StreamsConfig.consumerPrefix(ConsumerConfig.ISOLATION_LEVEL_CONFIG), "read_committed");
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Double> transactions = builder.stream(
"financial-transactions",
Consumed.with(Serdes.String(), Serdes.Double())
);
// Aggregate transaction amounts by account
KTable<String, Double> accountBalances = transactions
.groupByKey()
.aggregate(
() -> 0.0, // Initializer
(key, value, aggregate) -> aggregate + value, // Aggregator
Materialized.with(Serdes.String(), Serdes.Double())
);
accountBalances.toStream().to(
"account-balances",
Produced.with(Serdes.String(), Serdes.Double())
);
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
}
}Schema Registry Examples
Example 15: Avro Producer with Schema Registry
Produce Avro-encoded messages with schema validation.
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class AvroProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
props.put("schema.registry.url", "http://localhost:8081");
// Define Avro schema
String userSchema = "{"
+ "\"type\":\"record\","
+ "\"name\":\"User\","
+ "\"namespace\":\"com.example\","
+ "\"fields\":["
+ " {\"name\":\"id\",\"type\":\"string\"},"
+ " {\"name\":\"name\",\"type\":\"string\"},"
+ " {\"name\":\"age\",\"type\":\"int\"},"
+ " {\"name\":\"email\",\"type\":[\"null\",\"string\"],\"default\":null}"
+ "]}";
Schema.Parser parser = new Schema.Parser();
Schema schema = parser.parse(userSchema);
try (KafkaProducer<String, GenericRecord> producer = new KafkaProducer<>(props)) {
// Create Avro record
GenericRecord user = new GenericData.Record(schema);
user.put("id", "user123");
user.put("name", "John Doe");
user.put("age", 30);
user.put("email", "john@example.com");
ProducerRecord<String, GenericRecord> record =
new ProducerRecord<>("users-avro", "user123", user);
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.printf("Avro record sent: topic=%s partition=%d offset=%d%n",
metadata.topic(), metadata.partition(), metadata.offset());
} else {
System.err.println("Error sending Avro record: " + exception.getMessage());
}
});
}
}
}Example 16: Avro Consumer with Schema Registry
Consume and deserialize Avro messages.
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class AvroConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "avro-consumer-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
props.put("schema.registry.url", "http://localhost:8081");
props.put("specific.avro.reader", "false");
try (KafkaConsumer<String, GenericRecord> consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(Collections.singletonList("users-avro"));
while (true) {
ConsumerRecords<String, GenericRecord> records =
consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, GenericRecord> record : records) {
GenericRecord user = record.value();
System.out.printf("Consumed Avro record:%n");
System.out.printf(" ID: %s%n", user.get("id"));
System.out.printf(" Name: %s%n", user.get("name"));
System.out.printf(" Age: %d%n", user.get("age"));
System.out.printf(" Email: %s%n", user.get("email"));
}
}
}
}
}Kafka Connect Examples
Example 17: JDBC Source Connector Configuration
Stream database changes to Kafka using JDBC source connector.
{
"name": "postgres-source-connector",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
"tasks.max": "1",
"connection.url": "jdbc:postgresql://localhost:5432/mydb",
"connection.user": "postgres",
"connection.password": "password",
"table.whitelist": "users,orders,products",
"mode": "incrementing",
"incrementing.column.name": "id",
"topic.prefix": "postgres-",
"poll.interval.ms": "1000",
"transforms": "createKey,extractInt",
"transforms.createKey.type": "org.apache.kafka.connect.transforms.ValueToKey",
"transforms.createKey.fields": "id",
"transforms.extractInt.type": "org.apache.kafka.connect.transforms.ExtractField$Key",
"transforms.extractInt.field": "id"
}
}Deploy Connector:
# Create connector via REST API
curl -X POST http://localhost:8083/connectors \
-H "Content-Type: application/json" \
-d @postgres-source.json
# Check connector status
curl http://localhost:8083/connectors/postgres-source-connector/status
# List all connectors
curl http://localhost:8083/connectorsExample 18: Elasticsearch Sink Connector
Stream Kafka data to Elasticsearch for search and analytics.
{
"name": "elasticsearch-sink-connector",
"config": {
"connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
"tasks.max": "1",
"topics": "user-events,order-events,product-events",
"connection.url": "http://localhost:9200",
"connection.username": "elastic",
"connection.password": "password",
"type.name": "_doc",
"key.ignore": "false",
"schema.ignore": "true",
"behavior.on.null.values": "delete",
"behavior.on.malformed.documents": "warn",
"batch.size": "2000",
"max.buffered.records": "20000",
"linger.ms": "1000",
"read.timeout.ms": "120000",
"connection.timeout.ms": "30000"
}
}Example 19: Debezium CDC Connector (MySQL)
Capture database changes in real-time using Debezium.
{
"name": "mysql-debezium-connector",
"config": {
"connector.class": "io.debezium.connector.mysql.MySqlConnector",
"tasks.max": "1",
"database.hostname": "mysql-server",
"database.port": "3306",
"database.user": "debezium",
"database.password": "dbz",
"database.server.id": "184054",
"database.server.name": "mysql-prod",
"database.include.list": "inventory",
"table.include.list": "inventory.customers,inventory.orders",
"database.history.kafka.bootstrap.servers": "localhost:9092",
"database.history.kafka.topic": "schema-changes.inventory",
"snapshot.mode": "initial",
"snapshot.locking.mode": "minimal",
"include.schema.changes": "true",
"tombstones.on.delete": "true",
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "([^.]+)\\.([^.]+)\\.([^.]+)",
"transforms.route.replacement": "$3"
}
}CDC Benefits:
- Real-time data synchronization
- Event sourcing from existing databases
- Zero application code changes
- Capture inserts, updates, deletes
Production Patterns
Example 20: Dead Letter Queue (DLQ) Pattern
Handle failed messages by routing to DLQ topic.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
import java.time.Duration;
import java.util.*;
public class DLQConsumer {
private final KafkaConsumer<String, String> consumer;
private final KafkaProducer<String, String> dlqProducer;
private final String dlqTopic;
public DLQConsumer(String groupId, String inputTopic, String dlqTopic) {
this.dlqTopic = dlqTopic;
// Consumer configuration
Properties consumerProps = new Properties();
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
this.consumer = new KafkaConsumer<>(consumerProps);
consumer.subscribe(Collections.singletonList(inputTopic));
// DLQ producer configuration
Properties producerProps = new Properties();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
this.dlqProducer = new KafkaProducer<>(producerProps);
}
public void processMessages() {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
try {
// Attempt to process record
processRecord(record);
} catch (RetriableException e) {
// Transient error - retry
System.err.println("Retrying record: " + e.getMessage());
retryRecord(record);
} catch (NonRetriableException e) {
// Permanent error - send to DLQ
System.err.println("Sending to DLQ: " + e.getMessage());
sendToDLQ(record, e);
}
}
consumer.commitSync();
}
}
private void processRecord(ConsumerRecord<String, String> record)
throws RetriableException, NonRetriableException {
// Business logic that may throw exceptions
if (record.value() == null) {
throw new NonRetriableException("Null value not allowed");
}
// Process record...
}
private void retryRecord(ConsumerRecord<String, String> record) {
// Implement retry logic (exponential backoff, max retries, etc.)
}
private void sendToDLQ(ConsumerRecord<String, String> record, Exception error) {
// Add error metadata to headers
Headers headers = record.headers();
headers.add("dlq.error.message", error.getMessage().getBytes());
headers.add("dlq.error.class", error.getClass().getName().getBytes());
headers.add("dlq.original.topic", record.topic().getBytes());
headers.add("dlq.original.partition", String.valueOf(record.partition()).getBytes());
headers.add("dlq.original.offset", String.valueOf(record.offset()).getBytes());
ProducerRecord<String, String> dlqRecord =
new ProducerRecord<>(dlqTopic, null, record.key(), record.value(), headers);
dlqProducer.send(dlqRecord, (metadata, exception) -> {
if (exception != null) {
System.err.println("Failed to send to DLQ: " + exception.getMessage());
}
});
}
static class RetriableException extends Exception {
public RetriableException(String message) { super(message); }
}
static class NonRetriableException extends Exception {
public NonRetriableException(String message) { super(message); }
}
}Example 21: Outbox Pattern for Reliable Publishing
Ensure database and Kafka writes are atomic.
-- Database outbox table
CREATE TABLE outbox (
id UUID PRIMARY KEY,
aggregate_type VARCHAR(255) NOT NULL,
aggregate_id VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Application writes to both business table and outbox in same transaction
BEGIN;
INSERT INTO orders (id, customer_id, total)
VALUES ('order-123', 'customer-456', 99.99);
INSERT INTO outbox (id, aggregate_type, aggregate_id, event_type, payload)
VALUES (
uuid_generate_v4(),
'Order',
'order-123',
'OrderCreated',
'{"orderId": "order-123", "customerId": "customer-456", "total": 99.99}'::jsonb
);
COMMIT;Debezium Connector for Outbox:
{
"name": "outbox-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "localhost",
"database.port": "5432",
"database.user": "postgres",
"database.password": "password",
"database.dbname": "myapp",
"database.server.name": "myapp",
"table.include.list": "public.outbox",
"transforms": "outbox",
"transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter",
"transforms.outbox.table.field.event.id": "id",
"transforms.outbox.table.field.event.key": "aggregate_id",
"transforms.outbox.table.field.event.type": "event_type",
"transforms.outbox.table.field.event.payload": "payload",
"transforms.outbox.route.topic.replacement": "${routedByValue}",
"tombstones.on.delete": "false"
}
}Testing Strategies
Example 22: Kafka Streams Testing with TopologyTestDriver
Unit test Kafka Streams topology without running Kafka.
import org.apache.kafka.common.serialization.*;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.test.*;
import org.junit.jupiter.api.*;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
public class WordCountStreamTest {
private TopologyTestDriver testDriver;
private TestInputTopic<String, String> inputTopic;
private TestOutputTopic<String, Long> outputTopic;
@BeforeEach
public void setup() {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "test");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy:1234");
// Build topology
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> input = builder.stream("input");
input.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
.groupBy((key, word) -> word)
.count()
.toStream()
.to("output");
Topology topology = builder.build();
testDriver = new TopologyTestDriver(topology, props);
// Create test topics
inputTopic = testDriver.createInputTopic(
"input",
new StringSerializer(),
new StringSerializer()
);
outputTopic = testDriver.createOutputTopic(
"output",
new StringDeserializer(),
new LongDeserializer()
);
}
@AfterEach
public void tearDown() {
testDriver.close();
}
@Test
public void testWordCount() {
// Send test data
inputTopic.pipeInput("key1", "hello world");
inputTopic.pipeInput("key2", "hello kafka streams");
// Verify output
Map<String, Long> expectedCounts = Map.of(
"hello", 2L,
"world", 1L,
"kafka", 1L,
"streams", 1L
);
Map<String, Long> actualCounts = outputTopic.readKeyValuesToMap();
assertEquals(expectedCounts, actualCounts);
}
@Test
public void testEmptyInput() {
inputTopic.pipeInput("key", "");
assertTrue(outputTopic.isEmpty());
}
}Example 23: Integration Testing with Testcontainers
Integration test with real Kafka using Testcontainers.
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.*;
import org.junit.jupiter.api.*;
import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.*;
public class KafkaIntegrationTest {
private static KafkaContainer kafka;
@BeforeAll
public static void setUp() {
kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"));
kafka.start();
}
@AfterAll
public static void tearDown() {
kafka.stop();
}
@Test
public void testProducerConsumer() throws Exception {
String topic = "test-topic";
String testMessage = "integration-test-message";
// Producer
Properties producerProps = new Properties();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps)) {
producer.send(new ProducerRecord<>(topic, "key", testMessage)).get();
}
// Consumer
Properties consumerProps = new Properties();
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps)) {
consumer.subscribe(Collections.singletonList(topic));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));
assertEquals(1, records.count());
ConsumerRecord<String, String> record = records.iterator().next();
assertEquals(testMessage, record.value());
}
}
}---
Summary
These 23 examples cover:
1. Producers (5 examples): Sync/async, idempotent, custom partitioner, compression 2. Consumers (4 examples): Manual commit, rebalance handling, seek/replay, multi-topic 3. Kafka Streams (5 examples): WordCount, joins, windowing, exactly-once 4. Schema Registry (2 examples): Avro producer/consumer 5. Kafka Connect (3 examples): JDBC source, Elasticsearch sink, Debezium CDC 6. Production Patterns (2 examples): DLQ, outbox pattern 7. Testing (2 examples): Unit tests, integration tests
Each example is production-ready and demonstrates best practices for building robust, scalable Kafka applications.
Kafka Stream Processing Skill
A comprehensive Claude Code skill for building production-ready event-driven applications with Apache Kafka. This skill covers the complete Kafka ecosystem including producers, consumers, Kafka Streams, connectors, schema registry, and operational best practices.
Overview
Apache Kafka is the industry-standard distributed streaming platform for building real-time data pipelines and streaming applications. This skill provides deep expertise in:
- Kafka Producers: Publishing messages with configurable reliability and performance
- Kafka Consumers: Reading messages with consumer groups and offset management
- Kafka Streams: Stream processing DSL for stateful transformations and aggregations
- Kafka Connect: Integrating external systems with source and sink connectors
- Schema Registry: Managing data contracts and schema evolution
- Production Deployment: Cluster setup, monitoring, security, and performance tuning
What is Apache Kafka?
Apache Kafka is a distributed streaming platform that:
1. Publishes and Subscribes: Like a message queue or enterprise messaging system 2. Stores Streams: With fault-tolerant, durable storage 3. Processes Streams: Real-time stream processing as events occur
Core Capabilities
High Throughput
- Handle millions of messages per second
- Linear scalability with partitions
- Batching and compression for efficiency
Low Latency
- Single-digit millisecond latency
- Zero-copy transfers
- Efficient binary protocol
Durability and Reliability
- Configurable replication (typically 3x)
- Persistent storage with configurable retention
- Automatic failover and recovery
- At-least-once, exactly-once delivery semantics
Scalability
- Horizontal scaling via partitions
- Independent scaling of producers and consumers
- Elastic cluster expansion
Quick Start
Prerequisites
- Java 11+ (Kafka and clients)
- Apache Kafka installed (or Docker)
- Understanding of distributed systems concepts
Running Kafka Locally
# Start ZooKeeper (or use KRaft mode in Kafka 3.x+)
$ bin/zookeeper-server-start.sh config/zookeeper.properties
# Start Kafka broker
$ bin/kafka-server-start.sh config/server.properties
# Create a topic
$ bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic quickstart-events \
--partitions 3 --replication-factor 1
# Verify topic created
$ bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--describe --topic quickstart-eventsSimple Producer (Java)
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record =
new ProducerRecord<>("quickstart-events", "key1", "Hello Kafka!");
producer.send(record);
producer.close();Simple Consumer (Java)
import org.apache.kafka.clients.consumer.*;
import java.time.Duration;
import java.util.*;
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("quickstart-events"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Received: %s%n", record.value());
}
}Simple Kafka Streams Application
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.util.Properties;
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> textLines = builder.stream("input-topic");
KTable<String, Long> wordCounts = textLines
.flatMapValues(line -> Arrays.asList(line.toLowerCase().split("\\W+")))
.groupBy((key, word) -> word)
.count();
wordCounts.toStream().to("output-topic");
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();Architecture Overview
Kafka Cluster Architecture
┌─────────────────────────────────────────────────────────────┐
│ Kafka Cluster │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Broker 1 │ │ Broker 2 │ │ Broker 3 │ │
│ │ │ │ │ │ │ │
│ │ Topic A │ │ Topic A │ │ Topic B │ │
│ │ Part 0 │ │ Part 1 │ │ Part 0 │ │
│ │ (Leader) │ │ (Leader) │ │ (Leader) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ ZooKeeper Ensemble (or KRaft) │ │
│ │ (Metadata, Coordination, Leader Election) │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ ▼
│ │
┌────┴────┐ ┌─────┴────┐
│Producers│ │Consumers │
│ │ │(Groups) │
└─────────┘ └──────────┘Topic and Partition Model
Topic: user-events
┌─────────────────────────────────────────────┐
│ Partition 0: [msg0][msg3][msg6][msg9] │ → Consumer A
│ Partition 1: [msg1][msg4][msg7][msg10] │ → Consumer B
│ Partition 2: [msg2][msg5][msg8][msg11] │ → Consumer C
└─────────────────────────────────────────────┘
Replication:
Partition 0: Broker 1 (Leader), Broker 2, Broker 3
Partition 1: Broker 2 (Leader), Broker 1, Broker 3
Partition 2: Broker 3 (Leader), Broker 1, Broker 2Consumer Group Model
Topic with 4 partitions
┌──────┬──────┬──────┬──────┐
│ P0 │ P1 │ P2 │ P3 │
└──────┴──────┴──────┴──────┘
│ │ │ │
│ │ │ │
Consumer Group (CG-1)
│ │ │ │
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ C1 │ │ C2 │ │ C3 │
│ P0,P1 │ │ P2 │ │ P3 │
└───────┘ └───────┘ └───────┘
- Each partition consumed by exactly one consumer in group
- Consumers can process messages in parallel
- Adding more consumers (up to partition count) increases throughputKafka Streams Topology
Input Topics → Source Processors
↓
Stream Processors
(map, filter, join, aggregate)
↓
State Stores (optional)
(RocksDB, In-Memory)
↓
Sink Processors
↓
Output TopicsWhen to Use Kafka
Ideal Use Cases
Event-Driven Microservices
- Decouple services via events
- Asynchronous communication
- Event sourcing and CQRS
Real-Time Data Pipelines
- ETL between systems
- Data lake ingestion
- CDC (Change Data Capture)
Stream Processing
- Real-time analytics
- Fraud detection
- Real-time recommendations
- Monitoring and alerting
Log Aggregation
- Centralized logging
- Application metrics
- Audit trails
Message Queue Replacement
- High-throughput messaging
- Durable message storage
- Replay capability
When NOT to Use Kafka
Not Ideal For:
- Simple request-response patterns (use REST/gRPC)
- Small-scale applications (overhead not justified)
- Strict ordering across entire dataset (only per-partition ordering)
- Transactional databases (Kafka is not a database)
- File storage (use object storage like S3)
Key Concepts
Topics and Partitions
- Topic: Logical channel for messages (e.g., "user-events", "orders")
- Partition: Ordered, immutable log within a topic
- Offset: Unique sequential ID for each message in partition
- Replication: Each partition replicated across multiple brokers
Producers
- Publish records to topics
- Choose partition via key or custom partitioner
- Configure reliability (acks) and performance (batching)
- Support synchronous and asynchronous sends
Consumers
- Subscribe to topics and consume records
- Consumer groups for parallel processing
- Offset management (automatic or manual)
- Rebalancing for fault tolerance
Kafka Streams
- Library for building stream processing applications
- Supports stateless (map, filter) and stateful (aggregate, join) operations
- Exactly-once processing semantics
- Built-in state stores with changelog topics
Kafka Connect
- Framework for integrating external systems
- Source connectors: Import data into Kafka
- Sink connectors: Export data from Kafka
- Hundreds of pre-built connectors available
Schema Registry
- Centralized schema management
- Schema evolution with compatibility checking
- Support for Avro, JSON Schema, Protobuf
- Producer/consumer schema validation
Message Delivery Semantics
At-Most-Once (0-1 delivery)
- Messages may be lost but never duplicated
- Producer: acks=0, no retries
- Consumer: Commit offset before processing
- Use Case: Metrics, logs where occasional loss acceptable
At-Least-Once (1+ delivery)
- Messages never lost but may be duplicated
- Producer: acks=all, retries enabled
- Consumer: Commit offset after processing
- Use Case: Most applications with idempotent processing
Exactly-Once (1 delivery)
- Messages delivered exactly once
- Producer: Idempotent + transactions
- Consumer: isolation.level=read_committed
- Streams: processing.guarantee=exactly_once
- Use Case: Financial transactions, critical data
Performance Characteristics
Throughput
- Producers: Millions of messages/sec per broker
- Consumers: Limited by processing speed, not Kafka
- Scaling: Linear with partition count
Latency
- End-to-End: 2-10ms typical
- Producer: <5ms for async sends
- Consumer: Depends on fetch settings and processing
Storage
- Retention: Configurable by time or size
- Compression: gzip, snappy, lz4, zstd
- Compaction: Log compaction for changelog topics
Skill Contents
This skill includes:
1. Core Concepts: Topics, partitions, brokers, replication 2. Producer Guide: Configuration, best practices, examples 3. Consumer Guide: Groups, offsets, rebalancing, error handling 4. Kafka Streams: DSL, stateful processing, windowing, joins 5. Schema Registry: Avro, evolution, compatibility 6. Kafka Connect: Source/sink connectors, CDC 7. Performance Tuning: Batching, compression, threading 8. Production Deployment: Cluster setup, monitoring, security 9. Best Practices: Patterns, anti-patterns, operational guidance 10. Troubleshooting: Common issues and solutions
Examples Included
The skill includes 18+ comprehensive examples:
- Simple producer/consumer
- Exactly-once producer
- Consumer group coordination
- Manual offset management
- Kafka Streams WordCount
- Stateful stream processing
- Stream-stream joins
- Stream-table joins
- Windowed aggregations
- Avro serialization with Schema Registry
- JDBC source connector
- Elasticsearch sink connector
- CDC with Debezium
- Event sourcing pattern
- CQRS pattern
- Saga pattern
- Outbox pattern
- Dead letter queue pattern
Additional Resources
- SKILL.md: Complete reference guide with all concepts and examples
- EXAMPLES.md: Detailed code examples and patterns
- Apache Kafka Docs: https://kafka.apache.org/documentation/
- Confluent Platform: https://docs.confluent.io/
Version Information
- Skill Version: 1.0.0
- Kafka Versions: 2.x, 3.x
- Last Updated: October 2025
- Context7 Integration: Uses latest Apache Kafka documentation
Getting Help
For Kafka-specific questions:
- Apache Kafka mailing lists: https://kafka.apache.org/contact
- Confluent Community: https://forum.confluent.io/
- Stack Overflow:
apache-kafkatag
---
Start with SKILL.md for the complete guide, then explore EXAMPLES.md for practical implementations.
KAFKA-STREAM-PROCESSING SKILL - BUILD SUMMARY
==============================================
FILES CREATED:
--------------
1. SKILL.md - 49,215 bytes (48 KB) ✓ Target: 20 KB minimum
2. README.md - 14,171 bytes (14 KB) ✓ Target: 10 KB minimum
3. EXAMPLES.md - 51,707 bytes (50 KB) ✓ Target: 15 KB minimum
4. Total Size - 115,093 bytes (112 KB)
FILE LOCATIONS:
--------------
All files in: ~/Library/Application Support/Claude/skills/kafka-stream-processing/
VALIDATION CHECKLIST:
--------------------
✓ Valid YAML frontmatter (name, description, tags, tier)
✓ SKILL.md ≥ 20 KB (actual: 48 KB)
✓ README.md ≥ 10 KB (actual: 14 KB)
✓ EXAMPLES.md ≥ 15 KB (actual: 50 KB)
✓ 23 detailed examples (exceeds 18+ requirement)
✓ Context7 integration (18 APIDOC blocks, 59 code blocks)
SKILL STRUCTURE:
---------------
SKILL.md SECTIONS (1,374 lines):
- When to Use This Skill
- Core Concepts (Topics, Partitions, Semantics, Load Balancing)
- Producers (API, Configuration, Best Practices)
- Consumers (Groups, Configuration, Offset Management)
- Kafka Streams (Architecture, KStream/KTable, Joins, State)
- Schema Registry (Benefits, Formats, Evolution)
- Kafka Connect (Source/Sink Connectors, CDC)
- Topic Management (User/Internal Topics)
- Monitoring and Metrics (Producer/Consumer/Broker/Streams)
- Production Deployment (Cluster, HA, Security, Performance)
- Best Practices (Producer/Consumer/Streams/Topic/Operations)
- Common Patterns (Event Sourcing, CQRS, Saga, Outbox, etc.)
- Troubleshooting
- Migration Strategies
README.md SECTIONS (411 lines):
- Overview
- What is Apache Kafka
- Core Capabilities
- Quick Start
- Architecture Overview
- When to Use Kafka
- Key Concepts
- Message Delivery Semantics
- Performance Characteristics
- Skill Contents
- Examples Included
- Additional Resources
EXAMPLES.md (1,419 lines, 23 Examples):
Producer Examples (5):
1. Simple Synchronous Producer
2. Asynchronous Producer with Callbacks
3. Idempotent Producer (Exactly-Once)
4. Custom Partitioner
5. Producer with Compression
Consumer Examples (4):
6. Basic Consumer with Manual Commit
7. Consumer with Rebalance Listener
8. Consumer Seek and Replay
9. Multi-Topic Consumer with Pattern
Kafka Streams Examples (5):
10. WordCount with Kafka Streams
11. Stream-Stream Join
12. Windowed Aggregations (Tumbling, Hopping, Session)
13. Stream-Table Join with GlobalKTable
14. Exactly-Once Streams Processing
Schema Registry Examples (2):
15. Avro Producer with Schema Registry
16. Avro Consumer with Schema Registry
Kafka Connect Examples (3):
17. JDBC Source Connector Configuration
18. Elasticsearch Sink Connector
19. Debezium CDC Connector (MySQL)
Production Patterns (2):
20. Dead Letter Queue (DLQ) Pattern
21. Outbox Pattern for Reliable Publishing
Testing Strategies (2):
22. Kafka Streams Testing with TopologyTestDriver
23. Integration Testing with Testcontainers
KEY FEATURES COVERED:
--------------------
Core Kafka:
- Topics and partitions architecture
- Replication and fault tolerance
- Message delivery semantics (at-least-once, at-most-once, exactly-once)
- Producer/consumer load balancing
- Consumer groups and rebalancing
- Offset management strategies
Producers:
- Synchronous and asynchronous sends
- Idempotent producers
- Transactional producers
- Custom partitioning
- Batching and compression
- Error handling and retries
Consumers:
- Consumer groups coordination
- Manual and automatic offset commits
- Rebalance listeners
- Seek/replay functionality
- Multi-topic subscriptions
- Pattern-based subscriptions
Kafka Streams:
- KStream, KTable, GlobalKTable
- Stateless operations (map, filter, flatMap)
- Stateful operations (aggregate, count, reduce)
- Stream-stream joins
- Stream-table joins
- Windowing (tumbling, hopping, session)
- Exactly-once processing
- Topology optimization
- State stores and changelog topics
Schema Registry:
- Avro serialization/deserialization
- Schema evolution
- Compatibility modes
- Producer/consumer integration
Kafka Connect:
- JDBC source connector
- Elasticsearch sink connector
- Debezium CDC (Change Data Capture)
- Connector configuration
- Transforms and SMTs
Production Patterns:
- Dead Letter Queue (DLQ)
- Outbox pattern
- Event sourcing
- CQRS
- Saga pattern
- Fan-out pattern
- Windowed aggregations
Production Deployment:
- Cluster architecture
- High availability configuration
- Security (SSL/TLS, SASL)
- Performance tuning
- Monitoring and metrics
- Eligible Leader Replicas (ELR)
Testing:
- Unit testing with TopologyTestDriver
- Integration testing with Testcontainers
- Producer/consumer testing
- Streams topology testing
CONTEXT7 INTEGRATION:
--------------------
Total Context7 snippets: 18 APIDOC blocks
Integration points:
- Schema Registry data contracts
- Producer/Consumer API documentation
- Kafka Streams configuration
- Topic management
- Partition assignment
- Join co-partitioning requirements
- Internal topic configuration
- Monitoring metrics
- ELR (Eligible Leader Replicas)
- Topology naming best practices
CODE EXAMPLES BY LANGUAGE:
-------------------------
- Java: 23 comprehensive examples
- Bash: 10+ CLI examples
- JSON: 3 connector configurations
- SQL: 1 outbox table schema
- Properties: 15+ configuration examples
BEST PRACTICES DOCUMENTED:
-------------------------
- Producer: Idempotence, batching, compression, error handling
- Consumer: Manual offsets, rebalancing, error handling
- Streams: Explicit naming, state management, exactly-once
- Topic Design: Partitioning, replication, retention
- Operations: Monitoring, alerting, capacity planning, security
SKILL TIER: tier-1 (Production-Ready)
=====================================