KafkaIQ

10.4.1 – Subject Naming Strategies

Module: Section 10 – Kafka Ecosystem & Tools
Section: 10.4 – Schema Registry Operations
Goal: Understand how subject naming strategies impact schema organization and evolution


Introduction

Subject naming strategies determine how schemas are organized and versioned in Schema Registry. The strategy you choose impacts schema evolution, multi-tenancy, and operational complexity.

Why This Module Matters: Wrong naming strategy = messy schema organization, difficult migrations, and coupling issues.


What Is a Subject?

Subject: Logical grouping of schemas in Schema Registry.

Example:

Subject: "users-value"
  Version 1 → Schema ID 5
  Version 2 → Schema ID 12
  Version 3 → Schema ID 27

Subject = Schema Evolution Boundary: Compatibility rules apply per subject.


The Three Built-In Strategies

Schema Registry provides 3 built-in subject naming strategies:

  1. TopicNameStrategy (Default)
  2. RecordNameStrategy
  3. TopicRecordNameStrategy

1. TopicNameStrategy (Default)

How It Works

Subject Name: <topic>-key or <topic>-value

Example:

Topic: users
Key Subject:   users-key
Value Subject: users-value

Producer Configuration

Properties props = new Properties();
props.put("value.subject.name.strategy", 
    "io.confluent.kafka.serializers.subject.TopicNameStrategy");  // Default

Use Case

Single schema per topic:

Topic: users
  All messages use User schema (v1, v2, v3)

Example:

// All messages to "users" topic use User schema
User user1 = User.newBuilder().setUserId(1).build();
User user2 = User.newBuilder().setUserId(2).build();

producer.send(new ProducerRecord<>("users", "1", user1));
producer.send(new ProducerRecord<>("users", "2", user2));

// Subject: users-value (versions: 1, 2, 3)

Advantages

✅ Simple: One subject per topic
✅ Easy to understand: Topic = schema boundary
✅ Low overhead: Minimal subjects to manage
✅ Works for 95% of use cases


Disadvantages

❌ One schema per topic: Can't mix multiple event types in same topic
❌ Topic explosion: Need separate topic for each event type


When to Use

✅ Default choice for most applications
✅ One event type per topic (e.g., User events, Order events)
✅ Simple architectures


2. RecordNameStrategy

How It Works

Subject Name: <namespace>.<record-name>

Example:

Record: com.example.avro.User
Subject: com.example.avro.User

Record: com.example.avro.Order
Subject: com.example.avro.Order

Key Insight: Subject is based on schema name, not topic name.


Producer Configuration

Properties props = new Properties();
props.put("value.subject.name.strategy", 
    "io.confluent.kafka.serializers.subject.RecordNameStrategy");

Use Case

Multiple event types in one topic:

Topic: events
  Message 1: User schema  → Subject: com.example.avro.User
  Message 2: Order schema → Subject: com.example.avro.Order
  Message 3: User schema  → Subject: com.example.avro.User

Example

User Schema:

{
  "type": "record",
  "name": "User",
  "namespace": "com.example.avro",
  "fields": [...]
}

Order Schema:

{
  "type": "record",
  "name": "Order",
  "namespace": "com.example.avro",
  "fields": [...]
}

Producer Code:

// Both go to same topic, different subjects
User user = User.newBuilder().setUserId(1).build();
Order order = Order.newBuilder().setOrderId("123").build();

producer.send(new ProducerRecord<>("events", "user-1", user));
// → Subject: com.example.avro.User

producer.send(new ProducerRecord<>("events", "order-123", order));
// → Subject: com.example.avro.Order

Advantages

✅ Multi-schema topics: Mix multiple event types in one topic
✅ Schema reuse: Same schema used across multiple topics
✅ Reduced topic count: Fewer topics to manage
✅ Event-driven patterns: Natural fit for event sourcing


Disadvantages

❌ Complex compatibility: Must manage compatibility per schema, not per topic
❌ Consumer complexity: Consumers must handle multiple schemas
❌ Harder to understand: Subject != topic name


When to Use

✅ Event sourcing (multiple event types in events topic)
✅ Multi-tenancy (same schema, multiple topics)
✅ Schema reuse across topics
✅ CDC (Change Data Capture with multiple table schemas)


Consumer Handling Multiple Schemas

ConsumerRecords<String, Object> records = consumer.poll(Duration.ofMillis(100));

for (ConsumerRecord<String, Object> record : records) {
    Object value = record.value();
    
    if (value instanceof User) {
        User user = (User) value;
        processUser(user);
    } else if (value instanceof Order) {
        Order order = (Order) value;
        processOrder(order);
    } else {
        logger.warn("Unknown type: {}", value.getClass());
    }
}

3. TopicRecordNameStrategy

How It Works

Subject Name: <topic>-<namespace>.<record-name>

Example:

Topic: events
Record: com.example.avro.User
Subject: events-com.example.avro.User

Topic: events
Record: com.example.avro.Order
Subject: events-com.example.avro.Order

Key Insight: Combines topic name + record name.


Producer Configuration

Properties props = new Properties();
props.put("value.subject.name.strategy", 
    "io.confluent.kafka.serializers.subject.TopicRecordNameStrategy");

Use Case

Multiple schemas per topic + per-topic schema evolution:

Topic: events
  Subject: events-com.example.avro.User (v1, v2, v3)
  Subject: events-com.example.avro.Order (v1, v2, v3)

Topic: analytics
  Subject: analytics-com.example.avro.User (v1, v2)
  Subject: analytics-com.example.avro.Order (v1, v2)

Example

Same User schema used in two topics:

User user = User.newBuilder().setUserId(1).build();

producer.send(new ProducerRecord<>("events", "1", user));
// → Subject: events-com.example.avro.User

producer.send(new ProducerRecord<>("analytics", "1", user));
// → Subject: analytics-com.example.avro.User

Result: Same schema evolves independently per topic!


Advantages

✅ Per-topic evolution: Same schema evolves differently per topic
✅ Multi-schema topics: Like RecordNameStrategy
✅ Topic isolation: Schema changes in events don't affect analytics
✅ Fine-grained compatibility: Per-topic + per-schema control


Disadvantages

❌ Subject explosion: Many subjects to manage
❌ Complex: Hardest to understand
❌ Operational overhead: More subjects = more monitoring


When to Use

✅ Multi-tenancy with isolation (e.g., tenant-a-events, tenant-b-events)
✅ Per-topic schema evolution required
✅ Advanced architectures with strict isolation needs


Comparison Table

Strategy Subject Name Schemas per Topic Schema Reuse Complexity
TopicNameStrategy <topic>-value 1 ❌ Low
RecordNameStrategy <namespace>.<name> Many ✅ Medium
TopicRecordNameStrategy <topic>-<namespace>.<name> Many ✅ (isolated) High

Real-World Examples

Example 1: E-Commerce (TopicNameStrategy)

Architecture: One topic per event type.

Topic: users    → Subject: users-value
Topic: orders   → Subject: orders-value
Topic: payments → Subject: payments-value

Why?: Simple, one schema per topic, easy to understand.


Example 2: Event Sourcing (RecordNameStrategy)

Architecture: All events in one topic.

Topic: events
  Subject: com.ecommerce.UserCreated
  Subject: com.ecommerce.OrderPlaced
  Subject: com.ecommerce.PaymentProcessed

Why?: Preserve event order, multiple event types, event store pattern.


Example 3: Multi-Tenant SaaS (TopicRecordNameStrategy)

Architecture: Per-tenant topics with multiple schemas.

Topic: tenant-a-events
  Subject: tenant-a-events-com.app.User
  Subject: tenant-a-events-com.app.Order

Topic: tenant-b-events
  Subject: tenant-b-events-com.app.User
  Subject: tenant-b-events-com.app.Order

Why?: Tenant isolation, per-tenant schema evolution, security boundaries.


Custom Naming Strategies

Implementing Custom Strategy

public class CustomSubjectNameStrategy implements SubjectNameStrategy {
    
    @Override
    public String subjectName(String topic, boolean isKey, Object value) {
        if (value instanceof GenericRecord) {
            GenericRecord record = (GenericRecord) value;
            String namespace = record.getSchema().getNamespace();
            String name = record.getSchema().getName();
            
            // Custom: <env>-<topic>-<name>
            return String.format("%s-%s-%s.%s", 
                System.getenv("ENVIRONMENT"),  // dev, prod
                topic,
                namespace,
                name
            );
        }
        return topic + (isKey ? "-key" : "-value");
    }
}

Configuration:

props.put("value.subject.name.strategy", 
    "com.example.CustomSubjectNameStrategy");

Key Takeaways

  1. TopicNameStrategy (Default): <topic>-value. One schema per topic, simplest approach.

  2. RecordNameStrategy: <namespace>.<name>. Multiple schemas per topic, schema reuse.

  3. TopicRecordNameStrategy: <topic>-<namespace>.<name>. Per-topic schema evolution.

  4. Subject = Evolution Boundary: Compatibility rules apply per subject.

  5. Default Choice: TopicNameStrategy works for 95% of use cases.

  6. Event Sourcing: Use RecordNameStrategy for multiple event types in one topic.

  7. Multi-Tenancy: Use TopicRecordNameStrategy for per-tenant isolation.

  8. Custom Strategies: Implement SubjectNameStrategy interface for custom logic.

  9. Operational Impact: More subjects = more monitoring, more complexity.

  10. Choose Early: Changing strategies later requires migration.


What's Next?

Next Module: 10.4.2 – Schema Registry REST API

What We Covered:

  • ✅ Subject naming strategies
  • ✅ TopicNameStrategy (default)
  • ✅ RecordNameStrategy (multi-schema)
  • ✅ TopicRecordNameStrategy (isolated evolution)

What's Coming:

  • ❌ Schema Registry REST API endpoints
  • ❌ Monitoring Schema Registry
  • ❌ Troubleshooting

References:

  • Schema evolution → 10.3: Schema Evolution & Compatibility
  • Avro serialization → 10.2.2: Avro Serialization with Kafka
  • Operations → 10.4: Schema Registry Operations