Skip to content

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

Kafka Producer Guide

The Kafka producer is the client library responsible for publishing records to Kafka topics.


The producer handles serialization, partitioning, batching, and network communication with Kafka brokers.

Producer internals from application call to brokerProducer internals from application call to brokerKafka ProducerSerializersRecord AccumulatorKafka ClusterApplicationPartitionerSender ThreadNetwork ClientKeySerializerValueSerializerBatch(partition 0)Batch(partition 1)Batch(partition 2)Broker 1Broker 2Broker 3Batches accumulate until:- batch.size reached- linger.ms elapsedkeyvalueassign partitionbatches ready
ComponentResponsibility
SerializersConvert keys and values to bytes
PartitionerDetermine target partition for each record
Record AccumulatorBuffer records into batches per partition
Sender ThreadBackground thread that transmits batches to brokers
MetadataCache of cluster topology and partition leadership

Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092,kafka3:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "all");
try (Producer<String, String> producer = new KafkaProducer<>(props)) {
ProducerRecord<String, String> record = new ProducerRecord<>(
"orders", // topic
"order-123", // key
"{\"amount\": 100}" // value
);
// Synchronous send
RecordMetadata metadata = producer.send(record).get();
System.out.printf("Sent to partition %d offset %d%n",
metadata.partition(), metadata.offset());
// Asynchronous send with callback
producer.send(record, (meta, exception) -> {
if (exception != null) {
exception.printStackTrace();
} else {
System.out.printf("Sent to partition %d offset %d%n",
meta.partition(), meta.offset());
}
});
}
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=['kafka1:9092', 'kafka2:9092', 'kafka3:9092'],
key_serializer=lambda k: k.encode('utf-8'),
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
acks='all'
)
# Send record
future = producer.send(
'orders',
key='order-123',
value={'amount': 100}
)
# Block for synchronous send
record_metadata = future.get(timeout=10)
print(f"Sent to partition {record_metadata.partition} offset {record_metadata.offset}")
producer.close()

ConfigurationDefaultDescription
bootstrap.servers-Comma-separated list of broker addresses
key.serializer-Serializer class for record keys
value.serializer-Serializer class for record values
acksall (3.0+)Acknowledgment level: 0, 1, or all
retries2147483647Number of retry attempts
delivery.timeout.ms120000Upper bound on time to report success/failure
ConfigurationDefaultDescription
batch.size16384Maximum batch size in bytes
linger.ms0Time to wait for additional records
buffer.memory33554432Total memory for buffering
max.block.ms60000Time to block when buffer is full
ConfigurationDefaultDescription
enable.idempotencetrue (3.0+)Enable idempotent producer
max.in.flight.requests.per.connection5Max unacknowledged requests per connection
transactional.idnullTransaction ID for transactional producer
ConfigurationDefaultDescription
compression.typenoneCompression: none, gzip, snappy, lz4, zstd
max.request.size1048576Maximum request size in bytes
send.buffer.bytes131072TCP send buffer size
receive.buffer.bytes32768TCP receive buffer size

→ Configuration Reference


The acks configuration determines how many replicas must acknowledge a write before the producer considers it successful.

Acknowledgment behavior for acks=0, acks=1, and acks=allProducerLeaderFollower 1Follower 2ProducerProducerLeaderLeaderFollower 1Follower 1Follower 2Follower 2acks=0 (Fire and Forget)sendNo wait for ackacks=1 (Leader Only)sendackLeader writes, acks immediatelyacks=all (All ISR)sendreplicatereplicateackackackWait for all ISR
acksDurabilityLatencyUse Case
0Lowest—may lose dataLowestMetrics, logs where loss acceptable
1Medium—loses data if leader fails before replicationMediumLow latency, some durability
allHighest—survives broker failuresHighestProduction default for important data

acks=all Requires min.insync.replicas

With acks=all, the producer waits for all in-sync replicas. If min.insync.replicas is not set, this could mean waiting for only the leader if followers fall out of sync.


The partitioner determines which partition receives each record. Partitioning affects ordering, parallelism, and load distribution.

Default partitioner decision path for keyed and null-key recordsDefault partitioner decision path for keyed and null-key recordsKey is null?yesnoKafka 2.4+?yesnoBatch to single partitionuntil batch is fullSticky PartitionerRound-robinSame key always goesto same partitionhash(key) mod partitions
KeyBehaviorUse Case
With keyConsistent hashing—same key → same partitionOrdering by entity (user, order)
Null key (2.4+)Sticky partitioning—fills batch before switchingHigh throughput, no ordering needed
Null key (pre-2.4)Round-robinLegacy behavior
public class RegionPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
String region = extractRegion(value);
int numPartitions = cluster.partitionCountForTopic(topic);
// Partition 0-2 for US, 3-5 for EU, 6-8 for APAC
switch (region) {
case "US": return key.hashCode() % 3;
case "EU": return 3 + (key.hashCode() % 3);
case "APAC": return 6 + (key.hashCode() % 3);
default: return Math.abs(key.hashCode() % numPartitions);
}
}
}
ConsiderationGuidance
Ordering requirementsUse consistent keys for records that must be ordered together
Hot partitionsAvoid keys that create uneven distribution
Partition countMore partitions enable more parallelism but add overhead
Consumer parallelismMaximum consumer parallelism equals partition count

→ Partitioning Guide


Batching improves throughput by amortizing network and disk I/O overhead across multiple records.

Record accumulator batches released on batch.size or linger.msRecord accumulator batches released on batch.size or linger.msRecord AccumulatorPartition 0 QueuePartition 1 QueuePartition 2 QueueBatch (filling)Batch (full)Batch (filling)Batch (ready: linger.ms)Sender Threadbatch.size = 16KBlinger.ms = 5msbatch.size reachedlinger.ms elapsed
ConfigurationEffect of Increasing
batch.sizeLarger batches, more compression, higher latency
linger.msMore time to fill batches, higher latency
buffer.memoryMore batches can accumulate, handles bursts better
Workloadbatch.sizelinger.msRationale
Low latency163840-5Send immediately or with minimal wait
High throughput65536+10-100Fill larger batches for efficiency
Balanced327685-20Good throughput with acceptable latency

→ Batching Guide


Compression reduces network bandwidth and storage at the cost of CPU.

TypeCompression RatioCPU CostSpeed
none1.0xNoneFastest
gzipBest (0.3-0.5x)HighSlowest
snappyGood (0.5-0.7x)LowFast
lz4Good (0.5-0.7x)LowFastest
zstdBetter (0.4-0.6x)MediumFast
End-to-end compression from producer through broker to consumerEnd-to-end compression from producer through broker to consumerProducerBrokerConsumerBatchCompress(if configured)Store(compressed)DecompressProcessBroker stores datain producer's compressionformat (end-to-end)raw batchcompressed batchcompressed batchraw records
ScenarioRecommendedRationale
Network-constrainedgzip or zstdMaximum compression
CPU-constrainedlz4 or snappyLow CPU overhead
BalancedzstdGood ratio with reasonable CPU
Latency-sensitivelz4Fastest compression/decompression

→ Compression Guide


Idempotent producers prevent duplicate messages during retries by assigning sequence numbers to each record.

Idempotent producer deduplicating a retried batch by sequence numberProducerBrokerProducer(PID: 1000)Producer(PID: 1000)BrokerBrokersend(seq=0, batch=[A,B,C])ack(seq=0)send(seq=1, batch=[D,E])Network timeout, no ack receivedretry send(seq=1, batch=[D,E])seq=1 already processedack(seq=1, deduplicated)Broker tracks (PID, partition, seq)Duplicate sequences are ignored
# Enable idempotent producer (default in Kafka 3.0+)
enable.idempotence=true
# Implied settings when idempotence is enabled:
# acks=all
# retries=MAX_INT
# max.in.flight.requests.per.connection <= 5
ScopeCovered
Single sessionDuplicates prevented within producer session
Across restartsDuplicates possible—use transactions for exactly-once
Across partitionsEach partition tracked independently

→ Idempotence Guide


Transactions enable atomic writes to multiple partitions and exactly-once semantics when combined with transactional consumers.

Transactional producer flow from initTransactions to commit markersProducerTransactionBrokerBrokerProducerProducerTransactionCoordinatorTransactionCoordinatorBroker(partition 0)Broker(partition 0)Broker(partition 1)Broker(partition 1)initTransactions()PID assignedbeginTransaction()send(topic-A, partition-0)send(topic-B, partition-1)sendOffsetsToTransaction()(consumer offsets)commitTransaction()write COMMIT markerwrite COMMIT markercommit completeAll writes become visibleatomically when committed
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092");
props.put("transactional.id", "my-app-instance-1"); // Required
props.put("enable.idempotence", "true"); // Implied
Producer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("topic-a", "key", "value1"));
producer.send(new ProducerRecord<>("topic-b", "key", "value2"));
// Commit consumer offsets as part of transaction
producer.sendOffsetsToTransaction(offsets, consumerGroupMetadata);
producer.commitTransaction();
} catch (ProducerFencedException | OutOfOrderSequenceException e) {
producer.close();
} catch (KafkaException e) {
producer.abortTransaction();
}
GuaranteeScope
Atomic writesAll records in transaction succeed or fail together
Cross-partitionAtomicity across multiple partitions and topics
Exactly-once with consumerWhen combined with read_committed consumers

→ Transactions Guide


Error TypeExamplesProducer Behavior
RetriableNetworkException, NotEnoughReplicasExceptionAutomatic retry up to retries
Non-retriableInvalidTopicException, RecordTooLargeExceptionFail immediately
FatalProducerFencedException, OutOfOrderSequenceExceptionMust close producer
producer.send(record, (metadata, exception) -> {
if (exception == null) {
// Success
log.info("Sent to {} offset {}", metadata.partition(), metadata.offset());
} else if (exception instanceof RetriableException) {
// Retriable error - already retried and failed
log.error("Retriable error after retries: {}", exception.getMessage());
// May want to retry at application level or dead-letter
} else {
// Non-retriable error
log.error("Non-retriable error: {}", exception.getMessage());
// Handle bad data, configuration issues, etc.
}
});
ConfigurationDescription
delivery.timeout.msUpper bound on total time to report success/failure
request.timeout.msTime to wait for response to a request
retry.backoff.msTime to wait before retrying
retry.backoff.max.msMaximum backoff time

→ Error Handling Guide


PracticeRationale
Use appropriate batch.size and linger.msBalance throughput and latency
Enable compression for large messagesReduce network and storage
Reuse producer instancesConnection and buffer overhead
Use async sends with callbacksAvoid blocking on every send
PracticeRationale
Use acks=all for important dataSurvive broker failures
Enable idempotencePrevent duplicates on retry
Set appropriate delivery.timeout.msBound total delivery time
Handle callbacks/futures properlyDetect and handle failures
PracticeRationale
Monitor producer metricsDetect issues early
Use meaningful client.idEasier debugging and monitoring
Close producers properlyFlush buffers and release resources
Size buffer.memory appropriatelyHandle traffic bursts