KafkaListner
Topic Subscription
Kafka Annotation
Programming
Coding Tips

How to subscribe multiple topic using @KafkaListner annotation

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 for Apache Kafka, a single listener method can subscribe to multiple topics by using the topics attribute on @KafkaListener. This is useful when the handling logic is the same or nearly the same across several streams. The main design question is not whether it is possible, but whether one listener method is the right structure for your topic layout and message semantics.

Subscribe to Several Topics With topics

The direct approach is to provide an array of topic names.

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

With that configuration, the same method receives records from both topics. Spring creates a listener container that subscribes to the provided topic names, and Kafka group management assigns partitions to consumer instances.

Read Which Topic the Message Came From

If you subscribe to multiple topics, you often need to branch based on the source topic. Spring lets you inject record metadata through headers or through ConsumerRecord.

Using a header:

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.kafka.support.KafkaHeaders;
3import org.springframework.messaging.handler.annotation.Header;
4import org.springframework.stereotype.Component;
5
6@Component
7public class TopicAwareListener {
8
9    @KafkaListener(topics = {"billing.events", "shipping.events"})
10    public void listen(String payload, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
11        System.out.println("Topic: " + topic + ", payload: " + payload);
12    }
13}

That is often cleaner than parsing the topic from the raw consumer record unless you already need the full record metadata.

Use topicPattern for Dynamic Topic Sets

If the topic list changes often and follows a naming convention, a pattern can be a better choice than hard-coding names.

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.stereotype.Component;
3
4@Component
5public class AuditListener {
6
7    @KafkaListener(topicPattern = "audit\\..*")
8    public void listen(String message) {
9        System.out.println("Audit event: " + message);
10    }
11}

This subscribes to topics that match the pattern rather than a fixed list. It is useful for tenant-prefixed or environment-scoped topic families, but it should be used deliberately because it can pull in more topics than intended if the pattern is too broad.

Add Group and Concurrency Settings Carefully

In real applications, you usually specify a consumer group and sometimes concurrency as well.

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.stereotype.Component;
3
4@Component
5public class InventoryListener {
6
7    @KafkaListener(
8        topics = {"inventory.in", "inventory.adjustments"},
9        groupId = "inventory-service",
10        concurrency = "3"
11    )
12    public void listen(String message) {
13        System.out.println(message);
14    }
15}

The concurrency value creates multiple consumer threads in the listener container. That can increase throughput, but it only helps if the subscribed topics have enough partitions to keep those consumers busy.

When Separate Listeners Are Better

A single multi-topic listener is convenient when the message contract is the same across topics. If each topic needs very different deserialization, validation, retry handling, or business logic, separate listener methods are often clearer.

For example, combining JSON business events and plain text audit events in one method usually makes the code harder to reason about. Keeping them separate can simplify error handling and make configuration more explicit.

The rule of thumb is simple: share a listener when the behavior is genuinely shared, not just because the annotation allows it.

Property-Based Topic Names

Spring also allows externalized topic names, which is useful for environment-specific configuration.

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.stereotype.Component;
3
4@Component
5public class ConfigDrivenListener {
6
7    @KafkaListener(topics = {"${app.kafka.topic1}", "${app.kafka.topic2}"})
8    public void listen(String payload) {
9        System.out.println(payload);
10    }
11}

This keeps deployment-specific topic names out of the compiled code while preserving the same multi-topic listener pattern.

Common Pitfalls

The first pitfall is putting unrelated topics into one listener method just because it is possible. That often creates messy conditional logic and mixed error-handling behavior.

Another issue is confusing topics with topicPattern. Fixed names belong in topics; pattern-based subscription belongs in topicPattern. They are mutually exclusive choices.

Developers also expect concurrency alone to increase throughput. If the topics do not have enough partitions, extra listener threads do not add useful parallelism.

Finally, the annotation in the title is misspelled. In code, the Spring annotation is @KafkaListener, not @KafkaListner.

Summary

  • Use @KafkaListener(topics = {"topic1", "topic2"}) to subscribe one method to multiple topics.
  • Inject the received topic name when the handling logic depends on the source stream.
  • Use topicPattern only when you intentionally want regex-based subscription.
  • Add groupId and concurrency based on your consumer-group design and partition count.
  • Split listeners when topics have meaningfully different payloads or operational requirements.

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

All Rights Reserved.