No rewrites
Keep your production Kafka beans
Dynamic bean overrides are for applications with named Kafka beans that include production-only broker or auth settings. Bootstrap is rewritten in place, security is stripped, serialisers are preserved.
One property turns it on:
wiretap.test.bean-overrides.enabled=trueWith that one property, the override also applies listener-friendly consumer defaults:
auto.offset.resetis set toearliest- configured consumer
group.idvalues get a unique test suffix for the Spring context
What gets adjusted
When the feature is enabled, the library looks for these Spring Kafka beans:
ProducerFactoryConsumerFactoryKafkaTemplateKafkaAdminConcurrentKafkaListenerContainerFactoryDefaultShareConsumerFactoryandShareKafkaListenerContainerFactory(share groups, see share groups)
Those beans keep their original names. Existing @Qualifier values, @KafkaListener(containerFactory = "...") references, and any code that injects a named KafkaTemplate continue to resolve in the same way.
The library then points the Kafka client configuration at the shared broker and removes production security settings such as security.protocol, sasl.*, ssl.*, and nested client auth settings.
{
- "bootstrap.servers": "prod-kafka.example.internal:9093",
- "security.protocol": "SASL_SSL",
- "sasl.mechanism": "PLAIN",
- "sasl.*": "production auth settings",
+ "bootstrap.servers": "localhost:62672",
"key.serializer": StringSerializer.class,
"value.serializer": CustomEventSerializer.class
}Map-built configuration
The override does not require teams to use the standard spring.kafka.* properties. Applications that build Kafka config directly in Java maps are supported too:
@Bean("eventConsumerFactory")
public ConsumerFactory<String, CustomEvent> eventConsumerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, productionBootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, "event-consumer");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, CustomEventDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}With dynamic overrides enabled, that consumer factory keeps the key deserialiser, ErrorHandlingDeserializer, and delegate deserialiser. The broker, test-incompatible security settings, and listener-friendly consumer defaults are changed.
{
- "bootstrap.servers": "prod-kafka-map.example.invalid:9093",
- "security.protocol": "SASL_SSL",
- "sasl.mechanism": "PLAIN",
+ "bootstrap.servers": "localhost:62672",
"group.id": "event-consumer-wiretap-9f4c...",
"auto.offset.reset": "earliest",
"key.deserializer": StringDeserializer.class,
"value.deserializer": ErrorHandlingDeserializer.class,
"spring.deserializer.value.delegate.class": CustomEventDeserializer.class
}The same applies to producer factories built with custom serialisers:
@Bean("customEventKafkaTemplate")
public KafkaTemplate<String, CustomEvent> customEventKafkaTemplate() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, productionBootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CustomEventSerializer.class);
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(props));
}JSON, Avro and Protobuf
JSON configurations are handled the same way as any other map-built client. The post-processor keeps Spring Kafka JSON serialiser settings, including default value type and type-header settings:
{
- "bootstrap.servers": "prod-kafka-map.example.invalid:9093",
- "security.protocol": "SASL_SSL",
+ "bootstrap.servers": "localhost:62672",
"value.deserializer": JsonDeserializer.class,
"spring.json.value.default.type": "com.example.CustomEvent",
"spring.json.use.type.headers": false
}For Avro or Protobuf, enable the mock Schema Registry as usual:
wiretap.test.mock-schema-registry.enabled=true
wiretap.test.bean-overrides.enabled=trueIf an Avro or Protobuf producer or consumer factory was built with a production Schema Registry URL, the override points it at the mock registry and removes common registry auth settings. The post-processor recognises either serialiser by class name — KafkaAvro* or KafkaProtobuf* — so a KafkaProtobufSerializer/Deserializer factory is retargeted exactly like an Avro one:
{
- "bootstrap.servers": "prod-kafka-map.example.invalid:9093",
- "schema.registry.url": "https://schema-registry.example.internal",
- "basic.auth.credentials.source": "USER_INFO",
- "schema.registry.basic.auth.user.info": "prod:secret",
+ "bootstrap.servers": "localhost:62672",
+ "schema.registry.url": "mock://wiretap",
"value.serializer": KafkaAvroSerializer.class
}Listener factories
Named listener container factories are common in applications with several event types or acknowledgement modes. Dynamic overrides preserve the listener factory bean and update its consumer factory.
@Bean("eventKafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, CustomEvent> eventKafkaListenerContainerFactory(
ConsumerFactory<String, CustomEvent> eventConsumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, CustomEvent> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(eventConsumerFactory);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}Share consumer factories
A DefaultShareConsumerFactory is retargeted through its bootstrap-servers supplier, because the share factory exposes no updateConfigs. When it carries production security keys, the bean is replaced by a sanitised copy under the same name, with the original deserialisers and factory listeners, so @KafkaListener(containerFactory = "...") references still resolve. A ShareKafkaListenerContainerFactory gets the same group-id suffix as the classic factory, and each suffixed share group receives share.auto.offset.reset from wiretap.test.bean-overrides.share.auto-offset-reset (default earliest) before its containers start, so seeded fixtures reach share listeners too.
Consumer defaults & opt-out
Consumer factories are made safe for listener-driven tests by default:
{
"bootstrap.servers": "localhost:62672",
- "group.id": "event-consumer",
- "auto.offset.reset": "latest",
+ "group.id": "event-consumer-wiretap-9f4c...",
+ "auto.offset.reset": "earliest",
"value.deserializer": CustomEventDeserializer.class
}If a test class is deliberately testing committed offsets or shared group behaviour, opt out of one or both defaults:
wiretap.test.bean-overrides.consumer.auto-offset-reset=preserve
wiretap.test.bean-overrides.consumer.unique-group-ids=falseYou can also provide a deterministic suffix if stable group names are useful for debugging:
wiretap.test.bean-overrides.consumer.group-id-suffix=-my-suiteWhen to keep manual test beans
Keep a manual @TestConfiguration override when the test needs a different behaviour, not just a different broker:
- replacing the production serialiser or deserialiser with a test double
- changing the consumer group strategy for a specific test class
- removing application-specific listener advice, filters, interceptors, or error handling
- testing against a deliberately different topic layout