import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
@SpringBootApplication
public class KafkaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(KafkaDemoApplication.class, args);
}
}
// ==========================================
// 1. CODE EXAMPLES (Template & Listener)
// ==========================================
@RestController
@RequestMapping("/api/messages")
class MessageController {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@PostMapping("/send")
public String sendMessage(@RequestParam String message) {
// Using KafkaTemplate to send a message to the "orders-topic"
kafkaTemplate.send("orders-topic", message);
return "Message sent to Kafka successfully!";
}
}
class MessageConsumer {
// Using @KafkaListener to automatically consume messages from "orders-topic"
@KafkaListener(topics = "orders-topic", groupId = "order-processing-group")
public void listen(String message) {
System.out.println("Received message: " + message);
}
}# ==========================================
# 2. CONFIGURATION PROPERTIES (application.yml)
# ==========================================
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
# Serializes keys and values into bytes before sending to the broker
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
acks: all # Ensures maximum data durability
consumer:
# Deserializes bytes back into Java objects/strings upon receipt
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
group-id: order-processing-group
auto-offset-reset: earliest # Reads from the beginning if no previous offset exists
listener:
# Concurrency spins up multiple Kafka message listener threads to process partitions in parallel
concurrency: 3 // ==========================================
// 3. ERROR HANDLING & RETRY MECHANISMS
// ==========================================
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.retrytopic.RetryableTopic;
import org.springframework.kafka.annotation.DltHandler;
import org.springframework.util.backoff.FixedBackOff;
@Configuration
class KafkaErrorHandlerConfig {
// Automatically sets up Non-Blocking Retries and a Dead Letter Topic (DLT)
@RetryableTopic(
attempts = "3",
backoff = @org.springframework.kafka.retrytopic.BackOff(delay = 2000), // 2 seconds between retries
dltTopicSuffix = "-dlt"
)
@KafkaListener(topics = "payment-topic", groupId = "payment-group")
public void processPayment(String paymentMessage) {
if (paymentMessage.contains("fail")) {
throw new RuntimeException("Simulated payment processing failure");
}
System.out.println("Payment processed: " + paymentMessage);
}
// This method handles messages that failed all 3 attempts
@DltHandler
public void handleDeadLetterMessage(String failedMessage) {
System.err.println("Message moved to DLT after maximum retries: " + failedMessage);
}
}If you want to tailor this implementation, let me know:
- What data format are you passing? (JSON, String, Avro?)
- Should we configure blocking retries (pausing the consumer partition) or non-blocking retries (forwarding to retry topics)?
- Do you need help setting up integration tests using EmbeddedKafka?
Let me know what you’d like to explore next!