Spring for Apache Kafka

Spring for Apache Kafka 129 (https://github.com/spring-projects/spring-kafka 2,512 ) (Apache 2.0) wraps the Java client: KafkaTemplate sends, @KafkaListener methods receive, and listener containers own threads, commits and retries. Spring Boot 4.1.1 (20 August 2026) brings spring-kafka 4.1.1 and kafka-clients 4.2.1 through spring-boot-starter-kafka and configures both from application.yaml (here: bootstrap server, group id, earliest, group.protocol: consumer). BookNest's refunds service forwards every cancelled order to booknest.refund-requests:

clients/spring/src/main/java/com/example/booknest/RefundsApp.java (excerpt)Java
@SpringBootApplication
public class RefundsApp {
  private final KafkaTemplate<String, String> kafka;
  RefundsApp(KafkaTemplate<String, String> kafka) { this.kafka = kafka; }   // auto-configured
  public static void main(String[] args) { SpringApplication.run(RefundsApp.class, args); }
  @Bean static RecordFilterStrategy<String, String> cancelledOnly() {   // true = discard
    return r -> { var h = r.headers().lastHeader("event-type");
                  return h == null || !"order_cancelled".equals(new String(h.value())); };
  }
  @KafkaListener(topics = "booknest.order-events", concurrency = "3", filter = "cancelledOnly")
  void onCancelled(ConsumerRecord<String, String> event) {
    kafka.send("booknest.refund-requests", event.key(), event.value());
  }
}
Output
14:48:08 ntainer#0-2-C-1 ... partitions assigned: [booknest.order-events-0,
  booknest.order-events-1,
         booknest.order-events-2]
14:48:18 ntainer#0-1-C-1 ... partitions assigned: [booknest.order-events-1]
14:48:18 ntainer#0-0-C-1 ... partitions assigned: [booknest.order-events-2]

concurrency = "3" created three KafkaConsumers on threads #0-0 to #0-2. Under KIP-848 the broker gave all partitions to the first member, then moved one to each of the others ten seconds later without stopping it. Run for 40 seconds (clients/spring_run.sh), it wrote 5,890 requests, one per cancelled order. A listener that throws gets ten deliveries from DefaultErrorHandler, then is logged and skipped; add a DeadLetterPublishingRecoverer to keep failed records (compare Dead Letter Queues).