Spring-Kafka
Bootstrap Servers
Listener Configuration
Java Programming
Kafka Integration

How to pass multiple bootstrap servers for listener using spring-kafka

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Spring Kafka, a listener does not usually receive bootstrap servers directly through the @KafkaListener annotation. Instead, the listener container uses a consumer factory, and that consumer factory gets its bootstrap servers from application properties or from explicit bean configuration.

Where the Listener Gets Bootstrap Servers

Kafka clients expect bootstrap servers as a comma-separated host and port list. The list does not need to contain every broker in the cluster, but it should contain more than one reachable broker so startup is resilient.

In a typical Spring Boot application, the easiest configuration is:

properties
spring.kafka.bootstrap-servers=kafka-1:9092,kafka-2:9092,kafka-3:9092
spring.kafka.consumer.group-id=orders-group
spring.kafka.consumer.auto-offset-reset=earliest

That property is used by Spring Boot when it creates the default consumer factory and listener container factory. Any @KafkaListener that uses the default container factory will inherit those bootstrap servers automatically.

YAML Configuration Works Too

If you prefer YAML, the same value can be expressed as a list or as a comma-separated string. Both are common in Spring Boot projects.

yaml
1spring:
2  kafka:
3    bootstrap-servers:
4      - kafka-1:9092
5      - kafka-2:9092
6      - kafka-3:9092
7    consumer:
8      group-id: orders-group
9      auto-offset-reset: earliest

The key idea is unchanged: the listener itself is not the place where you pass the brokers. The listener uses the consumer infrastructure that Spring builds around it.

Example Listener

Once the bootstrap servers are configured, the listener stays focused on messages:

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.stereotype.Component;
3
4@Component
5public class OrderListener {
6
7    @KafkaListener(topics = "orders")
8    public void onMessage(String payload) {
9        System.out.println("Received: " + payload);
10    }
11}

Notice that there is no bootstrap-server list in the annotation. That is normal.

Custom Consumer Factory

If you need a listener to use a different cluster or a different set of properties, define a custom consumer factory and listener container factory.

java
1import java.util.HashMap;
2import java.util.Map;
3import org.apache.kafka.clients.consumer.ConsumerConfig;
4import org.apache.kafka.common.serialization.StringDeserializer;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
8import org.springframework.kafka.core.ConsumerFactory;
9import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
10
11@Configuration
12public class KafkaConsumerConfig {
13
14    @Bean
15    public ConsumerFactory<String, String> consumerFactory() {
16        Map<String, Object> props = new HashMap<>();
17        props.put(
18            ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
19            "kafka-1:9092,kafka-2:9092,kafka-3:9092"
20        );
21        props.put(ConsumerConfig.GROUP_ID_CONFIG, "orders-group");
22        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
23        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
24        return new DefaultKafkaConsumerFactory<>(props);
25    }
26
27    @Bean
28    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
29        ConsumerFactory<String, String> consumerFactory
30    ) {
31        ConcurrentKafkaListenerContainerFactory<String, String> factory =
32            new ConcurrentKafkaListenerContainerFactory<>();
33        factory.setConsumerFactory(consumerFactory);
34        return factory;
35    }
36}

This is the right approach when one application listens to multiple clusters or needs listener-specific infrastructure.

What Multiple Bootstrap Servers Actually Do

A common misconception is that the client keeps publishing or consuming through every bootstrap server in the list. It does not. The list is only the initial contact point. Once the client gets cluster metadata, it connects to the appropriate broker leaders for the partitions it needs.

That is why the list should be thought of as a discovery seed list, not as a load-balancing list.

Common Pitfalls

  • Trying to put bootstrap servers on @KafkaListener instead of on the consumer configuration.
  • Using only one broker in production, which creates an unnecessary startup dependency.
  • Assuming the bootstrap list must contain every broker in the cluster.
  • Forgetting that a custom listener container factory must actually be used by the listener if you define more than one.
  • Mixing producer and consumer configuration mentally. Listeners use consumer settings, not producer settings.

Summary

  • In Spring Kafka, listeners get bootstrap servers from the consumer factory, not from the annotation.
  • The simplest setup is spring.kafka.bootstrap-servers in properties or YAML.
  • Multiple bootstrap servers are passed as a comma-separated list or YAML list.
  • Use a custom consumer factory only when the default Boot configuration is not enough.
  • Bootstrap servers are for initial discovery, not for round-robin message handling.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design