skip to content

You need to publish several related event types (OrderCreated, OrderShipped, OrderCancelled) to a single topic to preserve their ordering. How do you configure subject naming and serialization to make this work?

level: seniorimportance: should knowfreq 45%

answer

  1. ordering => same topic + same key
  2. default subject collides for 2+ types
  3. use TopicRecordName or RecordName strategy
  4. 5-byte header carries schema ID per record
  5. consumer dispatches on concrete type

basics

~20 s

Switch off the default per-topic subject by setting value.subject.name.strategy to RecordNameStrategy or TopicRecordNameStrategy, so each event type gets its own subject. Also disable auto-union validation issues by using a schema that allows the multiple types (e.g. an Avro union).

solid answer

~40 s

The default TopicNameStrategy forces all values in a topic into one subject, so multiple distinct types collide. To put related events in one topic (for per-key ordering), set value.subject.name.strategy to TopicRecordNameStrategy (isolated lineage per type per topic) or RecordNameStrategy (shared lineage). Each event type then registers under its own subject and evolves independently. On the wire, each record still carries its own schema ID, so consumers resolve the exact type. With Avro you typically model the payload as a union of the event records (or use separate SpecificRecord classes) and dispatch on the deserialized type. Also set auto.register.schemas appropriately and ensure the deserializer can handle all types (e.g. use.latest.version / specific.avro.reader settings as needed). This is the canonical 'multiple event types in one topic' pattern.

go deeper

for a junior

Recognize that one topic can hold multiple types only with a non-default subject strategy.

for a middle

Name the strategy to set and why the default collides.

for a senior

Tie ordering motivation, strategy choice, and consumer-side type dispatch together coherently.

for a principal

Weigh share-vs-isolate, downstream tooling compatibility, and schema-reference modeling for an org-wide event-on-topic standard.

## Why you'd want multiple types in one topic Kafka guarantees ordering only **within a partition**, and records with the same key go to the same partition. If `OrderCreated`, `OrderShipped`, and `OrderCancelled` for the same order must be processed in order, they should share a topic and a key (the order ID). Splitting them across topics loses cross-type ordering. So you deliberately want **heterogeneous record types in one topic**. ## Why the default breaks `TopicNameStrategy` maps every value to the single subject `<topic>-value`. Registering `OrderCreated` then `OrderShipped` under the same subject makes the registry treat the second as an *evolution* of the first; under any real compatibility setting this fails (different fields/records aren't compatible evolutions). ## The fix: a record-aware strategy Set on the **producer** (and the value side): - `value.subject.name.strategy=io.confluent.kafka.serializers.subject.TopicRecordNameStrategy` — subjects become `orders-com.acme.OrderCreated`, `orders-com.acme.OrderShipped`, etc. Each type gets an independent lineage scoped to this topic. - or `RecordNameStrategy` — subjects are the bare FQNs, shared with any other topic using the same types. Now each type has its own subject, so each can be registered and evolved without colliding. ## Serialization mechanics - Every produced record is prefixed with the **5-byte Confluent wire format**: a magic byte `0x0` + a 4-byte big-endian schema ID. That ID points to the exact schema, regardless of subject strategy. So a consumer reading mixed types still gets the right schema per record. - With **Avro**, two common modeling approaches: (a) a top-level **union** schema `["OrderCreated","OrderShipped","OrderCancelled"]` so a single reader handles all; or (b) distinct `SpecificRecord` classes and runtime type dispatch. For the union approach, the value schema literally is the union and the strategy still registers per record name. - Relevant configs: `auto.register.schemas` (often left true in dev, false + pre-registered in prod), `use.latest.version`, and on consumers `specific.avro.reader=true` for generated classes. ## Consumer side The deserializer reads the schema ID, fetches the schema, and reconstructs the object. Application code then **branches on the concrete type** (Avro union member, Protobuf `oneof`/message type, or `instanceof` on SpecificRecord). It must tolerate every type that can appear. ## Edge cases / pitfalls - Forgetting to also handle keys: if keys are uniform (just the order ID) keep `key.subject.name.strategy` as default; only the value side needs the record strategy. - Choosing RecordNameStrategy when you actually want per-topic isolation (or vice versa) — re-derive from the share-vs-isolate trade-off. - Some Kafka Streams / ksqlDB and connector paths assume one type per topic; verify downstream tooling supports multi-type topics before adopting. - Schema references: with Protobuf/JSON you may register a wrapper schema that *references* each event schema; that interacts with the strategy choice.

  • Why not just use three separate topics instead?
    Separate topics lose cross-type ordering for a given key; events for one order could be consumed out of order. One topic + one key per order preserves the sequence within a partition.
  • How does the consumer pick the right deserialized type for each record?
    Each record's 5-byte header holds its schema ID; the deserializer fetches that schema and reconstructs the exact type, then code dispatches on the concrete class (Avro union member / Protobuf message / instanceof).

saying these in an interview costs you the question

  • Saying you can do this while leaving the default TopicNameStrategy in place.
  • Claiming you must use separate topics — that loses ordering and isn't the point.
  • Forgetting that each record carries its own schema ID so mixed types are resolvable.
  • Assuming all downstream tooling (Streams/connectors) handles multi-type topics.

context