πŸš€ Apache Kafka & Spring Boot: Complete Mastery & Scenario-Based Interview Guide

NOTE

Obsidian Import Ready: This document contains structured Markdown, syntax-highlighted code blocks, and native Obsidian callouts (> [!NOTE], > [!TIP], > [!WARNING], > [!SCENARIO], > [!KEY-CONCEPT]). Use it as your comprehensive study guide and interview playbook.


πŸ“Œ Table of Contents

  1. 1. πŸ—οΈ Kafka Core Architecture & Ecosystem Fundamentals
  2. 2. βš™οΈ Tricky Core Concepts & Internal Behaviors
  3. 3. πŸƒ Spring Boot 3 Integration & Hands-on Code Snippets
  4. 4. πŸ›‘οΈ Fault Tolerance, Retryable Topics & Dead Letter Topics (DLT)
  5. 5. 🧬 Avro & Schema Registry for Schema Evolution
  6. 6. 🎯 Senior Scenario-Based Interview Questions & Solutions

1. πŸ—οΈ Kafka Core Architecture & Ecosystem Fundamentals

KEY CONCEPT

Apache Kafka is an open-source, distributed event streaming platform built on a durable publish-subscribe (pub-sub) model [16-18, 74]. Rather than acting as a traditional ephemeral message queue (where messages are deleted upon consumption), Kafka persists events sequentially to disk with a configurable Time-To-Live (TTL) / retention policy [17-18].

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                             KAFKA CLUSTER                               β”‚
β”‚                                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚     BROKER 1 (Leader)    β”‚         β”‚    BROKER 2 (Follower)   β”‚      β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Syncs   β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚      β”‚
β”‚  β”‚ β”‚ Topic A / Partition 0β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ β”‚ Topic A / Partition 0β”‚ β”‚      β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚         β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ Writes / Reads
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚    Producer     β”‚               β”‚  Consumer Group  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Ecosystem Components

  • Cluster & Brokers: A Kafka Cluster consists of one or more servers called Brokers [22-24, 89]. Brokers handle request routing, partition storage, and replication [43].
  • Topics: Logical channels or categories to which producers write messages and consumers subscribe [40, 92-93].
  • Partitions: Topics are broken down into multiple Partitions for horizontal scalability and parallel processing [40, 95-96]. Data within a partition is strictly ordered and immutable.
  • Offsets: Sequential numerical IDs (0, 1, 2, …) assigned to messages as they arrive in a partition [24-25, 97]. Offsets track consumer progress and prevent duplicate reads [25, 98].
  • Consumer Groups: Logical sets of consumer instances working together to process records from a topic in parallel [41, 45, 99-100].
  • Zookeeper vs. KRaft:

    INFO

    Historically, Apache Zookeeper managed cluster metadata, broker registration, partition state, and leader elections [44, 60-63]. From Kafka 2.8+ onwards (via KIP-500), Kafka introduced KRaft (Kafka Raft Metadata mode) to make Kafka fully self-contained and eliminate Zookeeper in Kafka 4.0 [33, 61, 64-65].


2. βš™οΈ Tricky Core Concepts & Internal Behaviors

WARNING

The Golden Rule of Consumer Groups: One Partition = At Most One Active Consumer Instance within the same consumer group [2, 5].

Topic with 6 Partitions (P0 - P5)
========================================================================
Scenario A: 6 Consumer Instances  -->  1 Partition per Consumer (Optimal) [2]
Scenario B: 2 Consumer Instances  -->  3 Partitions per Consumer [3]
Scenario C: 8 Consumer Instances  -->  6 Active Consumers + 2 IDLE (Standby) [5]

Guarantees & Rebalancing

  • Ordering Guarantees:

    TIP

    Kafka ONLY guarantees ordering within a single partition, NOT globally across an entire topic [12, 14]. To maintain strict order for related events (e.g., updates for order_id = 101), producers must supply a consistent Partitioning Key [13].

  • Rebalancing Modes:
    • Eager Rebalancing: Pauses processing for all consumers in the group while recalculating assignments, causing temporary downtime [7-9].
    • Cooperative Sticky Rebalancing: Reassigns only affected partitions incrementally, keeping unaffected consumers running smoothly [9].
  • Replication & In-Sync Replicas (ISR):
    • Leader Partition: Handles all read and write requests [11, 23, 34].
    • Follower Partitions: Replicate data from the leader. Followers in sync with the leader form the In-Sync Replicas (ISR) pool eligible to become the next leader if the active leader crashes [23, 49, 52].

3. πŸƒ Spring Boot 3 Integration & Hands-on Code Snippets

πŸ› οΈ 1. Maven Dependencies (pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

βš™οΈ 2. Application Properties (application.yml)

server:
  port: 9191
 
spring:
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
    consumer:
      group-id: inventory-group
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: "com.example.dto,*"

πŸ“€ 3. Programmatic Topic Configuration (KafkaConfig.java)

package com.example.config;
 
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;
 
@Configuration
public class KafkaConfig {
 
    @Bean
    public NewTopic createOrderTopic() {
        return TopicBuilder.name("order-events")
                .partitions(5)
                .replicas(1)
                .build();
    }
}

πŸš€ 4. Producer Implementation with Asynchronous Callbacks

package com.example.service;
 
import com.example.dto.OrderEvent;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
 
@Service
public class OrderProducer {
 
    private final KafkaTemplate<String, Object> kafkaTemplate;
 
    public OrderProducer(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }
 
    public void sendOrder(String orderId, OrderEvent event) {
        // Asynchronous non-blocking send with key for partition routing [13, 176-178]
        CompletableFuture<SendResult<String, Object>> future = 
                kafkaTemplate.send("order-events", orderId, event);
 
        future.whenComplete((result, ex) -> {
            if (ex == null) {
                System.out.printf("Sent event [%s] to Partition %d at Offset %d%n",
                        orderId,
                        result.getRecordMetadata().partition(),
                        result.getRecordMetadata().offset());
            } else {
                System.err.printf("Failed to send event [%s]: %s%n", orderId, ex.getMessage());
            }
        });
    }
}

πŸ“₯ 5. Consumer Implementation with @KafkaListener

package com.example.service;
 
import com.example.dto.OrderEvent;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
 
@Service
public class OrderConsumer {
 
    @KafkaListener(topics = "order-events", groupId = "inventory-group")
    public void consumeOrder(OrderEvent event) {
        System.out.println("Received Order Event: " + event.getOrderId() + " | Status: " + event.getStatus());
    }
}

4. πŸ›‘οΈ Fault Tolerance, Retryable Topics & Dead Letter Topics (DLT)

WARNING

When consuming messages, transient errors (e.g., database timeout) shouldn’t cause permanent message loss. Non-retryable errors (e.g., corrupted payload) shouldn’t block the processing pipeline forever [287, 289].

[Main Topic] ────(Error)────► [Retry Topic 0] ────(Error)────► [Retry Topic 1] ────(Exceeded)────► [Dead Letter Topic (DLT)] [289, 301]

πŸ’» Non-Blocking Retry & DLT Code Example

package com.example.service;
 
import com.example.dto.OrderEvent;
import org.springframework.kafka.annotation.DltHandler;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.annotation.RetryableTopic;
import org.springframework.kafka.retrytopic.TopicSuffixingStrategy;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.retry.annotation.Backoff;
import org.springframework.stereotype.Service;
 
@Service
public class ResilientConsumer {
 
    @RetryableTopic(
        attempts = "4", // 1 initial attempt + 3 retries (N-1 order) [288-289]
        backoff = @Backoff(delay = 2000, multiplier = 2.0), // Exponential backoff: 2s, 4s, 8s [307]
        topicSuffixingStrategy = TopicSuffixingStrategy.SUFFIX_WITH_INDEX_VALUE,
        exclude = {NullPointerException.class} // Skip retry for fatal unrecoverable bugs [308]
    )
    @KafkaListener(topics = "payment-events", groupId = "payment-group")
    public void processPayment(OrderEvent event) {
        if ("INVALID_IP".equals(event.getIpAddress())) {
            throw new RuntimeException("Transient Payment Service Connection Error!");
        }
        System.out.println("Payment processed successfully for: " + event.getOrderId());
    }
 
    @DltHandler
    public void handleDeadLetter(OrderEvent event, 
                                 @Header(KafkaHeaders.RECEIVED_TOPIC) String topic,
                                 @Header(KafkaHeaders.OFFSET) long offset) {
        System.err.printf("ALERT: Message routed to DLT [%s] Offset %d: Order %s%n",
                topic, offset, event.getOrderId());
    }
}

5. 🧬 Avro & Schema Registry for Schema Evolution

KEY CONCEPT

Apache Avro is a compact binary serialization format defined by a JSON schema (.avsc) [316-317]. Confluent Schema Registry stores schemas centrally, enabling producers and consumers to evolve schemas independently without breaking contract compatibility [319-320].

Producer ──(1. Send Schema/ID)──► Schema Registry [319]
Producer ──(2. Binary Payload)──► Kafka Topic
Consumer ◄─(3. Fetch Schema ID)── Schema Registry [319]

πŸ“œ 1. Avro Schema (employee.avsc)

{
  "type": "record",
  "name": "Employee",
  "namespace": "com.example.dto",
  "fields": [
    { "name": "id", "type": "string" },
    { "name": "firstName", "type": "string" },
    { "name": "lastName", "type": "string" },
    { "name": "emailId", "type": "string" },
    { "name": "middleName", "type": ["null", "string"], "default": null }
  ]
}

βš™οΈ 2. Configuration for Schema Registry in Spring Boot (application.yml)

spring:
  kafka:
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: io.confluent.kafka.serializers.KafkaAvroSerializer
      properties:
        schema.registry.url: http://localhost:8081
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer
      properties:
        schema.registry.url: http://localhost:8081
        specific.avro.reader: true

6. 🎯 Senior Scenario-Based Interview Questions & Solutions

SCENARIO

Q1: You have a Kafka topic with 6 partitions and run a consumer group with 10 instance pods. How will Kafka assign partitions? What happens if 1 active consumer pod crashes?

Answer:

  1. Initial State: Kafka enforces the rule that one partition can have at most one active consumer in a group [2]. Therefore, 6 consumers will actively process 1 partition each, while 4 consumers will remain idle (on standby) [5].
  2. When Pod Dies: A Consumer Group Rebalance is triggered [6]. Zookeeper / Group Coordinator detects the heartbeat loss, promotes one idle standby consumer to active status, and assigns the orphaned partition to it [6].
  3. Performance Impact: Spawning 10 consumers for 6 partitions will not increase processing throughput [2, 6]. To scale processing, you must increase topic partitions.

SCENARIO

Q2: How do you enforce strict message ordering for customer transaction processing across microservices in Kafka?

Answer:

  1. Per-Partition Guarantee: Kafka guarantees order only within a partition, not across the whole topic [12, 14].
  2. Partitioning Key Strategy: Set a consistent partitioning key (e.g., customerId or orderId) on every message [13]. Kafka’s default murmur hash ensures that all records sharing the same key land on the exact same partition in sequential offset order [13].
  3. Single Partition Fallback: If global ordering across all entities is strictly required, the topic must be created with only 1 partition, sacrificed for parallelism [11-12].

SCENARIO

Q3: What causes β€œConsumer Lag”, and how do you detect and mitigate it in production?

Answer:

  1. Definition: Consumer Lag is the difference between the Log End Offset (latest message published by producer) and the Current Consumer Offset [208, 220-221].
  2. Detection: Monitored using tools like Offset Explorer / Kafka Tool, Prometheus metrics, or Confluent Control Center [105, 209].
  3. Mitigation Strategies:
    • Increase Topic Partitions and scale up Consumer Group instances proportionally [99-100].
    • Optimize consumer business logic (e.g., batch database writes, non-blocking asynchronous processing).
    • Increase max.poll.records or adjust consumer concurrency threads in Spring Boot (ConcurrentKafkaListenerContainerFactory).

SCENARIO

Q4: How do you handle schema changes (e.g., adding a new required field or renaming a field) without causing downstream consumer crashes?

Answer:

  1. Avoid Direct POJO Changes: Sending raw Jackson POJOs without contract validation causes SerializationException or Incompatible Schema errors [231, 315, 361].
  2. Use Avro & Schema Registry: Define contracts using Avro schemas (.avsc) [316-317].
  3. Maintain Compatibility Rules:
    • Always provide a default value ("default": null or "") when adding new fields [327, 362].
    • Schema Registry validates schema updates against compatibility modes (Backward/Forward/Full) before accepting new registration, preventing incompatible payload pushes [319-320, 361-363].

SCENARIO

Q5: What is the difference between At-Least-Once, At-Most-Once, and Exactly-Once Processing semantics in Kafka?

Answer:

  • At-Least-Once (Default): Consumer processes the message and commits offset after processing [25, 30]. If consumer crashes before committing, the record is re-read upon restart, causing potential duplicate processing (must design idempotent consumers).
  • At-Most-Once: Consumer commits offset immediately after fetching, before processing. If processing fails, the record is lost forever.
  • Exactly-Once Processing (EOS): Achieved via Producer Idempotence (enable.idempotence=true), transactional producers (transactional.id), and transactional consumer commit coordination across Kafka topics.

πŸ’‘ Quick Summary Checklist for Interviews

  • Know the hierarchy: Cluster -> Broker -> Topic -> Partition -> Offset [22-25]
  • Know partition-consumer cardinality: Active Consumers <= Partitions [2, 5]
  • Know message keys dictate partition routing & ordering [13]
  • Know @RetryableTopic + @DltHandler for spring-kafka fault tolerance [298-301]
  • Know Avro + Schema Registry for contract enforcement & zero-downtime schema evolution [315-320]