Skip to content
Apache Kafka best practices 5 min read

Topic Design & Naming Conventions

A topic is the most durable contract in a Kafka system: it outlives the services that produce to it, the teams that own it, and usually the engineers who created it. Names you pick in a hurry become permanent, partition counts you guess at cap your throughput, and a careless mix of event types makes the data unconsumable by anyone but the original author. This page covers how to design topics that scale and stay legible — naming conventions, single vs. multiple event types, partition count and key choice, retention vs. compaction, and schema ownership.

Naming conventions

A good topic name is self-describing, sortable, and safe to grant ACLs against without ambiguity. Adopt a hierarchical, lowercase, dot-delimited scheme that flows from broad to narrow: domain, then entity, then event type, then a version segment. The version segment is the cheapest insurance you will ever buy — it lets you cut v2 of a topic for a breaking schema change without disturbing the consumers still reading v1.

<domain>.<entity>.<event-type>.<version>

orders.order.created.v1
orders.order.cancelled.v1
payments.payment.settled.v1
inventory.stock.adjusted.v1

Topic names may contain only [a-zA-Z0-9._-], and you should avoid mixing . and _ in a single name — Kafka collides on metric names where the two characters are interchangeable. Pick dots and stay consistent.

GoodBadWhy the bad one hurts
orders.order.created.v1OrdersTopicNo version, no event type, mixed case
payments.payment.settled.v1payment_eventsMany event types in one name; underscores
inventory.stock.adjusted.v1prod-orders-new-finalEnvironment baked in; “new”/“final” rot fast
audit.user.login.v1topic1Tells a consumer nothing

Do not bake the environment (prod-, dev-) into topic names. Use separate clusters or namespaces per environment instead. Environment prefixes leak deployment details into your code and make promotion between stages a find-and-replace exercise. If you must share a cluster, use a single leading prefix applied by tooling, not by hand.

One event type per topic, or many?

The default should be one event type per topic. A topic whose records all share a single schema is trivially deserializable, gives accurate per-event lag and throughput metrics, and lets you set retention and ACLs tuned to that event. Mixing OrderCreated, OrderShipped, and OrderCancelled into one orders topic forces every consumer to deserialize records it then discards and couples unrelated consumers to one another’s volume.

The exception is when strict ordering across event types matters — for example, you must guarantee that OrderCreated is always processed before OrderCancelled for the same order. Ordering in Kafka is only guaranteed within a partition, so the only way to order different event types is to put them on the same topic, keyed by the same business key. In that case, use a schema container (an Avro/Protobuf union or a sealed Java type) so a single deserializer handles all variants.

// Sealed hierarchy lets one topic carry related events with safe deserialization
public sealed interface OrderEvent permits OrderCreated, OrderShipped, OrderCancelled {}

public record OrderCreated(String orderId, String customerId, long amountCents) implements OrderEvent {}
public record OrderShipped(String orderId, String carrier, String trackingId) implements OrderEvent {}
public record OrderCancelled(String orderId, String reason) implements OrderEvent {}

Choosing partition count and key

Partition count sets two things at once: the maximum parallelism of any consumer group (one active consumer per partition) and your throughput ceiling. Estimate peak throughput in MB/s, divide by a realistic per-partition rate (commonly 1–10 MB/s depending on record size and hardware), and round up with headroom. Resist over-partitioning — every partition costs broker file handles, memory, and rebalance time, and a topic with thousands of tiny partitions is slower end-to-end, not faster.

The key matters as much as the count. The producer hashes the key to pick a partition, so all records with the same key land on the same partition and are therefore ordered relative to each other. Choose a stable business key that matches your ordering and co-partitioning requirements — orderId to keep an order’s lifecycle in sequence, customerId to process a customer’s events serially.

// Same key -> same partition -> per-order ordering preserved
var record = new ProducerRecord<>("orders.order.created.v1", order.id(), event);
producer.send(record);

Increasing partition count later rehashes keys to new partitions and breaks per-key ordering from that point on. Size for growth up front; treat partition count as effectively immutable for ordered topics.

Retention vs. compaction

Every topic needs an explicit cleanup policy — never rely on broker defaults. The choice is between delete (time- or size-bounded retention) for event streams you replay within a window, and compact for changelog/state topics where you only care about the latest value per key.

PolicyUse forKey config
deleteEvent/fact streams, logs, metricsretention.ms, retention.bytes
compactLatest-state-per-key tables, KTable changelogscleanup.policy=compact, min.cleanable.dirty.ratio
compact,deleteState you keep but also expire by ageboth, plus delete.retention.ms
# Event stream: keep 7 days
kafka-topics.sh --bootstrap-server broker1:9092 --create \
  --topic orders.order.created.v1 --partitions 12 --replication-factor 3 \
  --config retention.ms=604800000 --config min.insync.replicas=2

# Changelog: keep only the latest value per key forever
kafka-topics.sh --bootstrap-server broker1:9092 --create \
  --topic inventory.stock.snapshot.v1 --partitions 12 --replication-factor 3 \
  --config cleanup.policy=compact --config min.insync.replicas=2

Output:

Created topic orders.order.created.v1.
Created topic inventory.stock.snapshot.v1.

A compacted topic requires every record to have a non-null key, and a null value acts as a tombstone that deletes the key. Keep that in mind when modelling deletions.

Schema per topic

Pair every topic with one registered schema and treat that schema as a versioned API. With a Schema Registry (Avro, Protobuf, or JSON Schema), set a compatibility mode — typically BACKWARD — so producers can add optional fields without breaking existing consumers. Make breaking changes by cutting a new .v2 topic and migrating, never by mutating the live schema. One schema per topic is exactly why one-event-type-per-topic is the default: it keeps the registry’s subject, the topic, and the consumer’s deserializer in a clean one-to-one relationship.

Best Practices

  • Use a hierarchical domain.entity.event.version name in lowercase with dots; always include a version segment so breaking changes get a new topic.
  • Keep environment names out of topic names — separate clusters or namespaces instead.
  • Default to one event type per topic; only co-locate event types when cross-type ordering is mandatory, and then use a single union/sealed schema.
  • Size partition count for peak throughput plus headroom and treat it as immutable on ordered topics, since growing it rehashes keys and breaks ordering.
  • Pick a stable business key that matches your ordering and co-partitioning needs.
  • Set an explicit cleanup policy on every topic: delete with retention for streams, compact for latest-state-per-key changelogs.
  • Register one schema per topic with BACKWARD compatibility and version breaking changes through a new topic.
Last updated June 1, 2026
Was this helpful?