Migrate
4.x to Wiretap 5.0
Same library, new name. 5.0.0 renames the coordinates, the packages, the annotation and the property prefix in one go — a mechanical change with no behavioural surprises, and no compatibility shims to lean on.
4.x to Wiretap 5.0
Versions up to 4.1.2 were published as io.gitlab.guisseppe:spring-kafka-test-utils. Those artifacts stay on Maven Central; new work happens under the wiretap coordinates. Everything your tests do carries over — the shared broker, topic files, bean overrides, listener readiness, the whole assertion vocabulary — under new names.
Six of the seven steps below are search-and-replace. The seventh, removed API, only affects suites that passed an EmbeddedKafkaBroker into the helpers.
The complete mapping
| 4.x | 5.0 |
|---|---|
io.gitlab.guisseppe:spring-kafka-test-utils | io.typecheck:wiretap-spring-boot |
io.gitlab.guisseppe.spring.kafka.test.utils.assertions.* | io.typecheck.wiretap.core.assertions.* |
io.gitlab.guisseppe.spring.kafka.test.utils.* (publisher, reader, await, latch, TopicCleaner) | io.typecheck.wiretap.core.* |
io.gitlab.guisseppe.spring.kafka.test.utils.* (the annotation, KafkaTestListeners) | io.typecheck.wiretap.spring.* |
SharedKafkaProperties | io.typecheck.wiretap.spring.WiretapTestProperties |
SharedKafkaBrokerHolder | io.typecheck.wiretap.spring.WiretapBrokerHolder |
SharedKafkaObjectMapper | io.typecheck.wiretap.spring.WiretapObjectMapper |
@SharedKafkaBroker | @EnableWiretapKafkaBroker |
spring.kafka.test.<key> | wiretap.test.<key> |
sharedKafkaTestObjectMapper | wiretapTestObjectMapper |
mock://shared-kafka-test | mock://wiretap |
send(broker, topic, key, value) | send(servers, topic, key, value) |
readRecords(broker, topic, …) | readRecords(servers, topic, …) |
assertThatTopic(topic, broker) | assertThatTopic(topic, servers) |
1. Coordinates
One dependency, in test scope. wiretap-spring-boot pulls in the wiretap-core engine, so a Spring Boot suite needs nothing else.
<dependency>
- <groupId>io.gitlab.guisseppe</groupId>
- <artifactId>spring-kafka-test-utils</artifactId>
- <version>4.1.2</version>
+ <groupId>io.typecheck</groupId>
+ <artifactId>wiretap-spring-boot</artifactId>
+ <version>5.2.3</version>
<scope>test</scope>
</dependency>Optionally import io.typecheck:wiretap-bom to align the versions of wiretap's own modules. It manages only wiretap artifacts, never third-party versions, so your own Spring Boot BOM stays in charge of everything else.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.typecheck</groupId>
<artifactId>wiretap-bom</artifactId>
<version>5.2.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>2. Imports
The library split into two modules in 5.0, and the packages follow that split. The Spring-free engine — publisher, reader, await helpers, latch, TopicCleaner, and everything under assertions — moved to io.typecheck.wiretap.core. The Spring integration — the annotation and KafkaTestListeners — moved to io.typecheck.wiretap.spring.
-import io.gitlab.guisseppe.spring.kafka.test.utils.SharedKafkaBroker;
-import io.gitlab.guisseppe.spring.kafka.test.utils.KafkaTestPublisher;
-import io.gitlab.guisseppe.spring.kafka.test.utils.KafkaTopicReader;
-import io.gitlab.guisseppe.spring.kafka.test.utils.assertions.KafkaAssertions;
+import io.typecheck.wiretap.spring.EnableWiretapKafkaBroker;
+import io.typecheck.wiretap.core.KafkaTestPublisher;
+import io.typecheck.wiretap.core.KafkaTopicReader;
+import io.typecheck.wiretap.core.assertions.KafkaAssertions;3. The annotation
@SharedKafkaBroker is now @EnableWiretapKafkaBroker. Its topics() and partitions() attributes are unchanged.
@SpringBootTest
-@SharedKafkaBroker(topics = {"orders", "payments"}, partitions = 2)
+@EnableWiretapKafkaBroker(topics = {"orders", "payments"}, partitions = 2)
class OrderServiceTest {4. Properties
Every spring.kafka.test.<key> becomes wiretap.test.<key>. The key names after the prefix are identical: topics, topics-file, partitions, mock-schema-registry.enabled, object-mapper-bean-name, listeners.ready-timeout, bean-overrides.enabled, bean-overrides.consumer.auto-offset-reset, bean-overrides.consumer.group-id-suffix, bean-overrides.consumer.unique-group-ids.
-spring.kafka.test.topics=orders,payments
-spring.kafka.test.partitions=2
-spring.kafka.test.mock-schema-registry.enabled=true
-spring.kafka.test.bean-overrides.enabled=true
-spring.kafka.test.listeners.ready-timeout=PT20S
+wiretap.test.topics=orders,payments
+wiretap.test.partitions=2
+wiretap.test.mock-schema-registry.enabled=true
+wiretap.test.bean-overrides.enabled=true
+wiretap.test.listeners.ready-timeout=PT20S
# Spring's own keys are untouched
spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}
logging.level.org.apache.kafka=WARN5. Renamed literals
Only relevant if your tests name them:
- the default object-mapper bean is
wiretapTestObjectMapper, notsharedKafkaTestObjectMapper - the mock schema registry URL is
mock://wiretap, notmock://shared-kafka-test - the generated consumer group-id suffix reads
-wiretap-<uuid>, so a test asserting on the old shape needs updating (or an explicitwiretap.test.bean-overrides.consumer.group-id-suffix)
6. Removed API
The overloads taking an EmbeddedKafkaBroker on KafkaTestPublisher, KafkaTopicReader and KafkaAssertions are gone. The engine is Spring-free and works against any cluster, so an address is the honest parameter. Pass broker.getBrokersAsString(), or drop the argument and use the implicit form.
-KafkaTestPublisher.send(broker, "orders", "order-1", order);
-KafkaTopicReader.readSingleRecord(broker, "orders", OrderEvent.class);
+KafkaTestPublisher.send(broker.getBrokersAsString(), "orders", "order-1", order);
+KafkaTopicReader.readSingleRecord(broker.getBrokersAsString(), "orders", OrderEvent.class);
// Or drop the argument entirely — the implicit forms use the broker under test
+KafkaTestPublisher.send("orders", "order-1", order);
+KafkaTopicReader.readSingleRecord("orders", OrderEvent.class);
// Keyless publish at an explicit address
+KafkaTestPublisher.sendValue(bootstrapServers, "orders", order);
+KafkaTestPublisher.send(bootstrapServers, "orders", null, order);Note the one asymmetry: there is no three-argument send(servers, topic, value), because (String, String, String) already means send(topic, key, value). Keyless at an explicit address is sendValue(servers, topic, value) or a null key. Injecting EmbeddedKafkaBroker as a bean still works in embedded mode — see Publishing records.
7. Verify
The migration is finished when the old vocabulary is gone and the suite is green. Two searches say so:
# Must return nothing
rg -n "io\.gitlab\.guisseppe|SharedKafkaBroker|spring\.kafka\.test\.|shared-kafka-test" \
src pom.xml build.gradle*
# Should still show your own, unchanged Spring configuration
rg -n "spring\.kafka\." src
./mvnw test # or: ./gradlew testDoing it with an agent
Every step above is mechanical, which makes it good work for a coding agent. The 5.0.0 release page carries a copy-pasteable migration prompt scoped to exactly these changes, and the skill pack teaches the agent the 5.0 API so it can finish the job rather than guess at it.
What 5.0 adds
Once the suite is green, two features are worth a look:
- Pass-through mode — run the same tests against a Testcontainers or local broker instead of the embedded one, with no code changes.
- Fixture seeding — declare the records a test starts from in a file, with
${placeholder}support, instead of publishing setup data by hand.
wiretap.test.mode=passthrough
wiretap.test.passthrough.bootstrap-servers=localhost:9092
wiretap.test.seed.locations=classpath:seeds/orders.json
wiretap.test.seed.mode=per-classComing from @EmbeddedKafka
If the suite still uses @EmbeddedKafka directly, the test logic stays the same. What changes is how the broker is started and how isolation is handled between methods.
Replace the annotation
@SpringBootTest
-@EmbeddedKafka(topics = {"orders", "payments"}, partitions = 2)
+@EnableWiretapKafkaBroker
class OrderServiceTest {If those topics are used across the suite, move them into test properties:
wiretap.test.topics=orders,payments
wiretap.test.partitions=2If they are specific to one class, keep them on the annotation instead:
@EnableWiretapKafkaBroker(topics = {"orders", "payments"}, partitions = 2)Keep your broker injection
If you already inject an EmbeddedKafkaBroker, that still works:
@Autowired
private EmbeddedKafkaBroker broker;Reading spring.kafka.bootstrap-servers is the more portable habit, though — it is published in both modes, where the broker bean exists only in embedded mode.
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;Remove manual bootstrap plumbing
The library sets both spring.kafka.bootstrap-servers and spring.embedded.kafka.brokers automatically in embedded mode. If you were wiring either value by hand purely for embedded Kafka tests, you can usually remove that setup.
Drop @DirtiesContext when it was only there for Kafka cleanup
With per-class embedded brokers, @DirtiesContext is often used as a blunt way to reset Kafka state. With a shared broker, records and consumer groups are cleared after each test method, so the extra context restart is usually no longer necessary.
@SpringBootTest
@EnableWiretapKafkaBroker
-@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
class CleanTest {Replace home-grown Kafka helpers where it helps
For reading records:
KafkaTopicReader.readSingleRecord(topic)for plain string payloadsKafkaTopicReader.readSingleRecord(topic, MyDto.class)for JSON DTOsKafkaTopicReader.readSingleRecord(topic, TradeEvent.class)for Avro SpecificRecordKafkaTopicReader.readSingleRecord(topic, GenericRecord.class)for Avro GenericRecord
For publishing test data:
KafkaTestPublisher.send(topic, key, value)for plain string recordsKafkaTestPublisher.send(topic, key, dto)for JSON, Avro or Protobuf payloads (auto-detected)KafkaTestPublisher.sendBatch(topic, List.of(dto1, dto2))for batches@KafkaSeedfor setup records that belong to the class rather than the method — see Seeding
Use dynamic bean overrides when you do not want test beans
Some teams have production Kafka beans that include authentication settings the embedded broker cannot use. If the test-only override exists only to swap those beans onto the shared broker, ask the library to do that instead:
wiretap.test.bean-overrides.enabled=trueSee Bean overrides for the full guide.
Decide whether to reuse the application's ObjectMapper
JSON DTO publishing, typed reading, JsonPath assertions and JSON snapshots use Jackson 3. By default they use the library's uniquely named wiretapTestObjectMapper bean, so normal application objectMapper beans do not collide. Point the library at yours only when the suite depends on its customisations — naming strategies, mix-ins, modules, custom serializers:
wiretap.test.object-mapper-bean-name=objectMapperThe named bean must be a Jackson 3 tools.jackson.databind.ObjectMapper. The default mapper already handles Java time fields, omits null values, ignores unknown fields, and allows null primitive values.
Keep @TestConfiguration overrides
If you use @TestConfiguration with @Primary beans to override Kafka clients for test purposes, there is nothing special to change. The broker properties are available before those beans are created.
@TestConfiguration
public class KafkaTestConfig {
@Primary
@Bean
public ConsumerFactory<String, String> testConsumerFactory(
@Value("${spring.kafka.bootstrap-servers}") String bootstrapServers) {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}
}Enable mock Schema Registry for Avro tests
wiretap.test.mock-schema-registry.enabled=trueMigration checklist
- replace
io.gitlab.guisseppe:spring-kafka-test-utilswithio.typecheck:wiretap-spring-bootin test scope - rewrite imports to
io.typecheck.wiretap.core[.assertions]andio.typecheck.wiretap.spring - replace
@SharedKafkaBrokerwith@EnableWiretapKafkaBroker - rename
spring.kafka.test.*towiretap.test.*, leaving Spring's own keys alone - update
sharedKafkaTestObjectMapperandmock://shared-kafka-testreferences, if your tests name them - replace
EmbeddedKafkaBrokerarguments with a bootstrap-servers address, or drop them - run the two verification searches, then the full suite
- coming from
@EmbeddedKafka: move shared topics into properties, remove@DirtiesContextthat only reset Kafka state, and remove redundant bootstrap-server wiring
Troubleshooting
Tests pass on their own but fail together
State is leaking somewhere outside Kafka. Check that:
- database or cache state is cleared between tests
- consumers are not reusing stale group IDs where that matters
- the test is not assuming records from a previous method are still present
A consumer does not see the expected message
Make sure the message is published and consumed in the same test method, or use a unique consumer group with auto.offset.reset=earliest. Topic definitions survive cleanup; payloads are cleared between test methods by design. For records that must be there at the start of every method, declare them with @KafkaSeed.
A class reports no broker at all
Check whether the class declares its own @ContextConfiguration. wiretap wires those contexts additively through a ContextCustomizerFactory, but such a class is configured before dynamic properties are registered — so any wiretap.test.* value it needs must come from @TestPropertySource, @SpringBootTest(properties) or application properties. See Core lifecycle.