Multi-Event Topics
Typr Events handles Kafka topics that carry multiple event types through sealed interfaces.
Directory-Based Groupingβ
Schemas in the same subdirectory become variants of a sealed interface:
schemas/
βββ order-events/ # Directory name β interface name
βββ OrderPlaced.avsc
βββ OrderUpdated.avsc
βββ OrderCancelled.avsc
Generated Sealed Interfaceβ
Java 21+β
public sealed interface OrderEvents
permits OrderPlaced, OrderUpdated, OrderCancelled {
}
public record OrderPlaced(...) implements OrderEvents { }
public record OrderUpdated(...) implements OrderEvents { }
public record OrderCancelled(...) implements OrderEvents { }
Kotlinβ
sealed interface OrderEvents
data class OrderPlaced(...) : OrderEvents
data class OrderUpdated(...) : OrderEvents
data class OrderCancelled(...) : OrderEvents
Scalaβ
sealed trait OrderEvents
case class OrderPlaced(...) extends OrderEvents
case class OrderUpdated(...) extends OrderEvents
case class OrderCancelled(...) extends OrderEvents
Type-Safe Pattern Matchingβ
Javaβ
switch (event) {
case OrderPlaced e -> processPlaced(e);
case OrderUpdated e -> processUpdated(e);
case OrderCancelled e -> processCancelled(e);
}
Kotlinβ
when (event) {
is OrderPlaced -> processPlaced(event)
is OrderUpdated -> processUpdated(event)
is OrderCancelled -> processCancelled(event)
}
Scalaβ
event match {
case e: OrderPlaced => processPlaced(e)
case e: OrderUpdated => processUpdated(e)
case e: OrderCancelled => processCancelled(e)
}
Exhaustive Handler Interfaceβ
The generated handler interface enforces exhaustive handling:
public interface OrderEventsHandler {
void handleOrderPlaced(String key, OrderPlaced event, StandardHeaders headers);
void handleOrderUpdated(String key, OrderUpdated event, StandardHeaders headers);
void handleOrderCancelled(String key, OrderCancelled event, StandardHeaders headers);
}
When you add a new event type:
- Add
OrderRefunded.avsctoschemas/order-events/ - Regenerate code
- Compilation fails until you implement
handleOrderRefunded
Unified Serdeβ
A single serializer/deserializer handles all event types:
// Serde works for any OrderEvents variant
OrderEventsSerde serde = OrderEventsSerde.instance();
// Serializes based on actual type
byte[] bytes = serde.serializer().serialize("topic", orderPlaced);
byte[] bytes = serde.serializer().serialize("topic", orderCancelled);
// Deserializes to correct variant
OrderEvents event = serde.deserializer().deserialize("topic", bytes);
Topic Definitionβ
public final class Topics {
public static final TypedTopic<String, OrderEvents> ORDER_EVENTS =
new TypedTopic<>("order-events", OrderEventsSerde.instance());
}
Standalone Recordsβ
Schemas at the root level (not in a subdirectory) generate standalone records without a sealed interface:
schemas/
βββ order-events/ # β sealed interface
β βββ ...
βββ Address.avsc # β standalone record
// No interface, just a record
public record Address(
String street,
String city,
String zipCode
) { }