Skip to content
Kafka Is Fun

Enterprise Architecture · Enterprise · Topic 4 of 5

Multi-Tenant Kafka Configuration

One way to isolate tenants is by topic name (a tenant id in the topic) and, where isolation matters more, a consumer factory per tenant. A topicPattern subscription lets one listener cover all tenants' topics; the group discovers new matching topics through periodic metadata refreshes (metadata.max.age.ms, 5 minutes by default).

TenantKafkaConfig.javajava
@Configuration
@Slf4j
public class TenantKafkaConfig {

    // One listener subscribes to every topic matching the pattern
    @KafkaListener(topicPattern = "tenant\\..*\\.order\\.created",
                   groupId = "multi-tenant-processor")
    public void onTenantOrder(
        ConsumerRecord<String, OrderEvent> record,
        @Header(KafkaHeaders.RECEIVED_TOPIC) String topic
    ) {
        // Extract the tenant from the topic: tenant.{tenantId}.order.created
        String tenantId = topic.split("\\.")[1];
        tenantOrderService.process(tenantId, record.value());
    }
}

// Publisher: routes to the tenant's topic
@Service
public class TenantOrderPublisher {

    public void publish(String tenantId, OrderEvent event) {
        String topic = String.format("tenant.%s.order.created", tenantId);
        kafkaTemplate.send(topic, event.getOrderId(), event);
    }
}

// Per-tenant consumer factory (for complete isolation)
@Bean
public Map<String, ConcurrentKafkaListenerContainerFactory<?, ?>> tenantFactories(
    Map<String, TenantConfig> tenants
) {
    return tenants.entrySet().stream()
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            entry -> {
                var config = entry.getValue();
                Map<String, Object> props = new HashMap<>(commonProps);
                props.put(BOOTSTRAP_SERVERS_CONFIG, config.getKafkaBrokers());
                props.put(GROUP_ID_CONFIG, config.getTenantId() + "-processor");
                var cf = new DefaultKafkaConsumerFactory<>(props);
                var factory = new ConcurrentKafkaListenerContainerFactory<>();
                factory.setConsumerFactory(cf);
                return factory;
            }
        ));
}

Sources

Checked against Spring for Apache Kafka 4.1.1, Spring Boot 4.1.1 and Kafka 4.2 documentation on 11 October 2026. Draft: not yet reviewed by a Kafka specialist, and releases move on, so confirm against the current documentation before you act on it.