Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Application Development

This section covers application development with Apache Kafka—from basic producer and consumer patterns to advanced error handling and production best practices.


Kafka provides multiple approaches for application development, each suited to different use cases:

ApproachUse CaseComplexity
Producer/Consumer APIsDirect Kafka integrationLow
Kafka StreamsStream processing within applicationsMedium
Kafka ConnectData integration without codeLow
Schema RegistrySchema-governed messagingMedium
Choosing between Kafka Connect, Kafka Streams, and the producer and consumer APIsChoosing between Kafka Connect, Kafka Streams, and the producer and consumer APIsApplication requirementMoving data between systems?yesnoUse Kafka ConnectComplex stream processing?yesnoUse Kafka StreamsSimple produce or consume?yesnoUse Producer/Consumer APIEvaluate hybrid approach

Kafka clients are available for all major programming languages:

LanguageClientMaintainer
Javakafka-clientsApache Kafka
Pythonconfluent-kafka-pythonConfluent
Goconfluent-kafka-goConfluent
Node.jskafkajsCommunity
.NETconfluent-kafka-dotnetConfluent
C/C++librdkafkaConfluent
RustrdkafkaCommunity

Most non-Java clients are built on librdkafka, a high-performance C library that provides consistent behavior across languages.

Kafka client library sitting between application code and the broker connectionsKafka client library sitting between application code and the broker connectionsApplicationKafka ClusterApplication CodeKafka Client LibraryBroker 1Broker 2Broker 3Client handles:- Connection pooling- Metadata refresh- Partitioning- Batching- Compression- Retriesproduce/consumenetworknetworknetwork

Producers write records to Kafka topics. Key concepts:

ConceptDescription
RecordKey-value pair with optional headers and timestamp
PartitioningRecords are assigned to partitions by key hash or explicit assignment
BatchingRecords are batched for efficiency before sending
AcknowledgmentsConfigurable durability guarantees (acks=0, 1, all)
IdempotenceExactly-once producer semantics (enable.idempotence=true)
// Basic producer example
Properties props = new Properties();
props.put("bootstrap.servers", "kafka:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record = new ProducerRecord<>("topic", "key", "value");
producer.send(record, (metadata, exception) -> {
if (exception != null) {
// Handle error
} else {
// Success: metadata.partition(), metadata.offset()
}
});

Consumers read records from Kafka topics. Key concepts:

ConceptDescription
Consumer GroupConsumers with same group.id share partitions
Partition AssignmentEach partition assigned to one consumer in group
OffsetPosition in partition; consumer tracks progress
CommitPersist offset to mark records as processed
RebalancePartition reassignment when consumers join/leave
// Basic consumer example
Properties props = new Properties();
props.put("bootstrap.servers", "kafka:9092");
props.put("group.id", "my-consumer-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// Process record
process(record.key(), record.value());
}
consumer.commitSync();
}

Local development setup with a single-node Kafka cluster in Docker ComposeLocal development setup with a single-node Kafka cluster in Docker ComposeDeveloper MachineDocker ComposeApplicationKafkaZooKeeper/KRaftSchema Registrydocker-compose up -dSingle-node cluster for developmentlocalhost:9092localhost:8081

docker-compose.yml for development:

version: '3'
services:
kafka:
image: confluentinc/cp-kafka:7.5.0
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk'
schema-registry:
image: confluentinc/cp-schema-registry:7.5.0
ports:
- "8081:8081"
environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: kafka:9092
depends_on:
- kafka
LevelApproachTools
UnitMock Kafka clientsMockito, embedded Kafka
IntegrationTestcontainerstestcontainers-kafka
End-to-endReal clusterStaging environment

Testcontainers example:

@Testcontainers
class KafkaIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
);
@Test
void shouldProduceAndConsume() {
Properties props = new Properties();
props.put("bootstrap.servers", kafka.getBootstrapServers());
// ... test implementation
}
}

SettingDevelopmentProduction
acks1all
retries02147483647
enable.idempotencefalsetrue
linger.ms05-100
batch.size1638465536-131072
compression.typenonelz4 or zstd
SettingDevelopmentProduction
auto.offset.resetearliestearliest or latest
enable.auto.committruefalse (manual commit)
max.poll.records500Tune based on processing time
session.timeout.ms4500045000
heartbeat.interval.ms30003000

PitfallProblemSolution
Not closing producersResource leaks, message lossAlways call producer.close()
Blocking in poll loopRebalance timeoutsProcess quickly or use separate threads
Ignoring errorsSilent data lossImplement error handlers, use callbacks
Auto-commit with at-least-onceMessage loss on crashUse manual commit after processing
Single consumer for high volumeBackpressureScale consumers to partition count
No idempotenceDuplicates on retryEnable enable.idempotence=true

Complete guide to building Kafka producers:

  • Client configuration and tuning
  • Serialization strategies
  • Partitioning and key design
  • Batching and compression
  • Error handling and retries
  • Transactions and exactly-once

Complete guide to building Kafka consumers:

  • Consumer groups and partition assignment
  • Offset management strategies
  • Rebalance handling
  • Concurrent processing patterns
  • Error handling and dead letter queues
  • Graceful shutdown

Stream processing library for building event-driven applications:

  • DSL for stream transformations
  • Stateful processing and state stores
  • Windowing and aggregations
  • Joins between streams and tables
  • Exactly-once semantics