Enterprise Java

Kafka Message Acknowledgement Options

Apache Kafka is a distributed messaging system widely used for building real-time data pipelines and streaming applications. To ensure reliable message delivery, Kafka provides various message acknowledgment options for both producers and consumers. These options help balance performance, durability, and fault tolerance based on application needs. Producers can control how messages are acknowledged by brokers, while consumers manage message processing guarantees. Understanding these acknowledgment mechanisms is crucial for designing efficient Kafka-based systems. Let us delve into understanding Kafka message acknowledgment options and explore how they impact reliability, message ordering, and system resilience through practical Java examples.

1. Introduction

Apache Kafka is a distributed event streaming platform designed for high-throughput, fault-tolerant, and real-time data processing. It enables applications to publish, subscribe, store, and process streams of records efficiently. Kafka is widely used in various domains, including real-time analytics, log aggregation, event-driven architectures, and data pipelines.

1.1 Benefits of Apache Kafka

  • Scalability: Kafka supports horizontal scaling by adding more brokers to the cluster.
  • Fault Tolerance: Data replication ensures message durability even if a broker fails.
  • High Throughput: Kafka processes millions of messages per second with low latency.
  • Durability: Messages are stored persistently, ensuring reliability.
  • Flexibility: Kafka integrates seamlessly with big data and cloud-based platforms.

2. Producer Acknowledgements Options

Kafka producers can configure acknowledgments using the acks parameter. This setting determines when a producer considers a message as successfully sent.

  • acks=0: The producer does not wait for acknowledgment from the broker. The message is sent asynchronously without waiting for confirmation, making it the fastest option but with a higher risk of data loss if the broker fails.
  • acks=1: The producer waits for acknowledgment from the leader only. If the leader broker receives the message and confirms it, the producer considers it successful. However, if the leader fails before replicating the data, the message might be lost.
  • acks=all (or -1): The producer waits for acknowledgment from all in-sync replicas (ISR). This ensures the highest level of durability, as messages are not considered sent until all replicas have confirmed receipt. This option provides strong consistency but may introduce latency.

2.1 Choosing the Right Acknowledgment Strategy

Choosing the right acknowledgment level depends on the application requirements:

  • For high throughput with low latency and tolerance for message loss, use acks=0.
  • For a balance between reliability and performance, use acks=1.
  • For the highest durability and no data loss, use acks=all.

2.2 Java Code Example for Kafka Producer

Let’s examine a code example:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.apache.kafka.clients.producer.*;
 
import java.util.Properties;
 
public class KafkaProducerExample {
    public static void main(String[] args) {
        String topicName = "test-topic";
 
        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");
 
        // Setting acknowledgment level to 'all'
        props.put("acks", "all");
 
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
 
        for (int i = 1; i <= 5; i++) {
            ProducerRecord<String, String> record = new ProducerRecord<>(topicName, "Key" + i, "Message" + i);
            producer.send(record, (metadata, exception) -> {
                if (exception == null) {
                    System.out.println("Sent message to topic: " + metadata.topic() +
                                       " | Partition: " + metadata.partition() +
                                       " | Offset: " + metadata.offset());
                } else {
                    System.err.println("Error while producing: " + exception.getMessage());
                }
            });
        }
        producer.close();
    }
}

2.2.1 Code Explanation and Output

This Java program demonstrates a simple Kafka producer that sends messages to a Kafka topic named “test-topic.” It first sets up a Properties object with essential Kafka configurations, including the bootstrap server (localhost:9092), key and value serializers (StringSerializer), and acknowledgment level (acks=all) to ensure message durability. A KafkaProducer<String, String> instance is created using these properties, and a loop iterates five times to send messages with keys (Key1 to Key5) and values (Message1 to Message5). Each message is wrapped in a ProducerRecord and sent asynchronously using producer.send(), which includes a callback to log metadata (topic, partition, and offset) on successful delivery or print an error message if the send fails. Finally, the producer is closed to release resources.

1
2
3
4
5
Sent message to topic: test-topic | Partition: 0 | Offset: 10
Sent message to topic: test-topic | Partition: 1 | Offset: 8
Sent message to topic: test-topic | Partition: 2 | Offset: 15
Sent message to topic: test-topic | Partition: 0 | Offset: 11
Sent message to topic: test-topic | Partition: 1 | Offset: 9

2.3 Challenges in Producer Acknowledgments

  • Latency vs. Reliability: Higher acknowledgment levels increase reliability but add latency.
  • Message Duplication: Retries may lead to duplicate messages.
  • Leader Failures: Messages may be lost if the leader crashes before replication.

3. Consumer Acknowledgements Options

Kafka consumers use manual or automatic acknowledgments to ensure message processing reliability. Consumers track their read positions using offsets, and acknowledgment determines when the offset is committed.

  • enable.auto.commit=true: The consumer automatically commits offsets at a regular interval. This setting reduces overhead but risks message loss in case of failure, as messages are considered processed even before actual consumption.
  • enable.auto.commit=false: The consumer manually commits offsets after processing messages. This approach ensures reliability by committing offsets only when processing is complete, preventing message loss but requiring additional management.

Keep in mind that when enable.auto.commit=false, Kafka offers two methods for committing offsets:

  • commitSync(): Commits offsets synchronously, ensuring they are written to Kafka before proceeding. This approach guarantees message acknowledgment but may introduce latency.
  • commitAsync(): Commits offsets asynchronously, improving performance but risking data loss if the application crashes before completion.

3.1 Choosing the Right Acknowledgment Strategy for Consumers

Choosing the right acknowledgment strategy depends on your application’s needs:

  • For simple processing where some message loss is acceptable, use enable.auto.commit=true.
  • For critical applications requiring precise message handling, use enable.auto.commit=false with commitSync().
  • For high-performance scenarios with reduced acknowledgment overhead, use commitAsync(), but handle potential duplicate processing.

3.2 Java Code Example for Kafka Consumer

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
 
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
 
public class KafkaConsumerExample {
    public static void main(String[] args) {
        String topicName = "test-topic";
 
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "test-group");
        props.put("key.deserializer", StringDeserializer.class.getName());
        props.put("value.deserializer", StringDeserializer.class.getName());
 
        // Disabling auto-commit for manual offset handling
        props.put("enable.auto.commit", "false");
 
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Collections.singletonList(topicName));
 
        try {
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.println("Consumed message: " + record.value() +
                                       " | Offset: " + record.offset());
                }
 
                // Manually committing offsets
                consumer.commitSync();
            }
        } finally {
            consumer.close();
        }
    }
}

3.2.1 Code Explanation and Output

This Java program demonstrates a simple Kafka consumer that subscribes to a topic named “test-topic” and processes incoming messages. It initializes a Properties object with essential Kafka configurations, including the bootstrap server (localhost:9092), consumer group ID (test-group), and deserializes for both keys and values (StringDeserializer). Auto-commit is disabled (enable.auto.commit=false) to allow manual offset management. A KafkaConsumer<String, String> instance is created and subscribed to the topic, after which it continuously polls for messages with a timeout of 100 milliseconds. For each consumed message, the value and offset are printed, and offsets are committed manually using commitSync() to ensure reliability. Finally, the consumer is closed when exiting to release resources.

1
2
3
4
5
Consumed message: Message1 | Offset: 10
Consumed message: Message2 | Offset: 8
Consumed message: Message3 | Offset: 15
Consumed message: Message4 | Offset: 11
Consumed message: Message5 | Offset: 9

4. Conclusion

Kafka provides flexible acknowledgment options to balance performance and reliability. Producers can use acks=0 for speed or acks=all for durability. Consumers can use enable.auto.commit=true for simplicity or manually commit offsets for reliability. Choosing the right acknowledgment strategy depends on application requirements.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button