Definition
- Request-reply -- if a caller needs an immediate answer, such as a fraud check, a direct REST or gRPC call is simpler than publishing a request and correlating a reply through Kafka.
- Low volume -- for a few hundred messages a day, Kafka's operational cost dwarfs the benefit; a managed queue like SQS gives the same decoupling with almost no operations.
- Priority and routing -- per-message priority, complex header-based routing, and per-message TTL are native in RabbitMQ or ActiveMQ but require awkward workarounds in Kafka.
- Small teams -- running Kafka well requires understanding partitions, replication, consumer groups, and rebalancing; a small team is better served by a managed queue or a fully managed Kafka service.
- Transactional joins -- if the real need is atomically updating several tables with ad-hoc queries and foreign keys, that is a database job, not a log.
Kafka is like building a dedicated high-speed conveyor system through National Bank of India that keeps a permanent record of every item and lets many departments read it at their own pace. That is fantastic for a flood of transactions feeding many systems. But if a teller just needs a yes-or-no answer from the credit desk right now, you do not build a conveyor, you make a phone call. And if the mailroom only handles a few letters a day, buying and maintaining an industrial conveyor is wasteful.
Understanding the Concepts
When would you choose NOT to use Kafka?
Kafka is a distributed, partitioned, replicated commit log optimized for high-throughput streaming, replayable event history, and many consumers. You deliberately avoid it when its strengths do not match the problem.
Why not use Kafka for synchronous request-reply?
If a caller needs an immediate answer, such as a fraud check that must return approve or decline before a transaction proceeds, a direct REST or gRPC call is simpler and clearer. You can do request-reply over Kafka, but it adds correlation ids, reply topics, and latency for no benefit when a simple call suffices.
package com.bank.service;
import java.math.BigDecimal;
// CORRECT: synchronous fraud check via direct call
public class FraudCheckService {
public boolean checkTransaction(String accountId, BigDecimal amount) {
// Direct REST call -- immediate response, no Kafka overhead
System.out.println("Checking fraud for " + accountId + " amount: " + amount);
boolean approved = amount.compareTo(new BigDecimal("1000000")) < 0;
System.out.println("Result: " + (approved ? "APPROVED" : "DECLINED"));
return approved;
}
public static void main(String[] args) {
FraudCheckService service = new FraudCheckService();
// Rajesh's card swipe needs instant decision -- REST, not Kafka
service.checkTransaction("SAV2001", new BigDecimal("50000"));
}
}
Expected output:
Checking fraud for SAV2001 amount: 50000
Result: APPROVED
Why not use Kafka for low-volume workloads?
Kafka is a stateful distributed system with brokers, replication, and the KRaft controller quorum to operate, patch, and monitor. If you move a few hundred or a few thousand messages a day, the operational cost dwarfs the benefit, and a managed queue such as Amazon SQS gives the same decoupling with almost no operations.
| Aspect | Kafka | Managed Queue (SQS) |
|---|---|---|
| Messages per day | Millions+ | Hundreds to thousands |
| Operations burden | High (brokers, replication, monitoring) | Near zero |
| Cost at low volume | High (cluster always running) | Pay per message |
| Use case fit | High-throughput streaming | Simple decoupling |
package com.bank.service;
// WRONG: using Kafka for 10 end-of-day notification emails
// A Kafka cluster running 24/7 for 10 messages/day is over-engineering
// CORRECT: use a managed queue
public class EndOfDayNotifier {
public void sendNotification(String accountId, String message) {
// Send to SQS or a simple job table -- no Kafka cluster needed
System.out.println("Queued notification for " + accountId + ": " + message);
}
public static void main(String[] args) {
EndOfDayNotifier notifier = new EndOfDayNotifier();
notifier.sendNotification("SAV2001", "Daily statement ready for Rajesh");
}
}
Expected output:
Queued notification for SAV2001: Daily statement ready for Rajesh
Why not use Kafka for priority queues and complex routing?
Kafka has no native priority queue and no native per-message TTL. If you need per-message priority, where urgent VIP wire transfers jump ahead of ordinary transfers, or complex routing by headers and patterns, RabbitMQ or ActiveMQ handle these out of the box. Simulating them with multiple topics and custom logic in Kafka is awkward.
package com.bank.service;
// WRONG: simulating priority in Kafka with multiple topics
// transfers-high, transfers-medium, transfers-low
// Consumer must poll all three and prioritize manually -- awkward
// CORRECT: use RabbitMQ with native priority queue
public class PriorityTransferExample {
public static void main(String[] args) {
// RabbitMQ queue declared with x-max-priority: 10
// Each message carries a priority header
System.out.println("VIP wire for Priya: priority 10 -- processed first");
System.out.println("Regular transfer for Rajesh: priority 5 -- processed after VIP");
// RabbitMQ delivers highest priority first natively
}
}
Expected output:
VIP wire for Priya: priority 10 -- processed first
Regular transfer for Rajesh: priority 5 -- processed after VIP
Why not use Kafka for small teams?
Running Kafka well means understanding partitions, replication, in-sync replicas, consumer groups, rebalancing, retention, and monitoring lag. A small team without that capacity is better served by a fully managed queue or even a database-backed job table. If you do need Kafka's model, a fully managed Kafka service removes most of the operational burden.
package com.bank.service;
// For a small team: a database-backed job table is simpler than Kafka
public class DatabaseJobQueue {
public void enqueue(String jobType, String payload) {
// INSERT INTO jobs (type, payload, status) VALUES (?, ?, 'PENDING')
System.out.println("Enqueued job: " + jobType + " -> " + payload);
}
public void processNext() {
// SELECT ... WHERE status='PENDING' ORDER BY created_at LIMIT 1 FOR UPDATE
System.out.println("Processing next pending job from database");
}
public static void main(String[] args) {
DatabaseJobQueue queue = new DatabaseJobQueue();
queue.enqueue("TRANSFER", "SAV2001 -> SAV3001 amount 25000");
queue.processNext();
}
}
Expected output:
Enqueued job: TRANSFER -> SAV2001 -> SAV3001 amount 25000
Processing next pending job from database
Why not use Kafka for transactional database work?
If you need to atomically update several business tables, run ad-hoc queries, enforce foreign keys, or join across entities, that is a database job. Kafka is a log, not a query engine, so forcing relational, transactional work through it is the wrong tool.
package com.bank.service;
import java.math.BigDecimal;
// CORRECT: use database transactions for multi-table atomic operations
public class TransferService {
// @Transactional -- one database transaction for all three operations
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
// debit(fromAccount, amount); -- UPDATE accounts SET balance = balance - ?
// credit(toAccount, amount); -- UPDATE accounts SET balance = balance + ?
// insertAuditLog(fromAccount, toAccount, amount); -- INSERT INTO audit_log
System.out.println("Atomic transfer: " + fromAccount + " -> "
+ toAccount + " amount: " + amount);
// All three succeed or all three roll back -- database ACID, not Kafka
}
public static void main(String[] args) {
TransferService service = new TransferService();
service.transfer("SAV2001", "SAV3001", new BigDecimal("50000"));
}
}
Expected output:
Atomic transfer: SAV2001 -> SAV3001 amount: 50000
When SHOULD you use Kafka?
Kafka shines for high-throughput ingestion, event sourcing, replayable history, stream processing, and fan-out to many independent consumers.
| Use Case | Right Tool | Why |
|---|---|---|
| Fraud check (instant response) | REST / gRPC | Synchronous request-reply |
| 10 emails/day | SQS / managed queue | Low volume |
| VIP wire priority | RabbitMQ | Native priority queue |
| Multi-table ACID | Database transaction | Relational joins, foreign keys |
| 50K transactions/second firehose | Kafka | High throughput, replay, fan-out |
| Event sourcing with replay | Kafka | Immutable log, replayable history |
| Analytics + fraud + ledger from same stream | Kafka | Multiple independent consumers |
What should a senior engineer know?
These are not absolutes. Kafka adds an asynchronous hop and at-least-once semantics that force you to design for idempotency and out-of-order delivery, which is unnecessary complexity when the interaction is naturally synchronous and exactly one caller cares about the result. The decision is about matching the tool to the access pattern. A fully managed Kafka service changes the operational calculus for small teams.
Visual Overview
Decision Tree: Should You Use Kafka?
=====================================
┌──────────────────────┐
│ Need immediate reply? │
└──────────┬───────────┘
yes │ no
┌──────┐ │ ┌──────────────────┐
│ REST │ │ │ High volume? │
│ gRPC │ │ │ (1000+ msg/sec) │
└──────┘ │ └────────┬──────────┘
│ yes │ no
│ ┌────────┘ ┌──────────────┐
│ │ │ Managed Queue │
│ │ │ (SQS, etc.) │
│ │ └──────────────┘
│ ▼
┌──────────────────────┐
│ Need priority queue? │
│ Complex routing? │
└──────────┬───────────┘
yes │ no
┌──────────┐ ┌──────────────┐
│ RabbitMQ │ │ Kafka │
│ ActiveMQ │ │ (correct │
└──────────┘ │ choice) │
└──────────────┘
Key Takeaway
Kafka is a distributed commit log optimized for high throughput, replayable history, stream processing, and fanning out to many independent consumers. I choose not to use it when the problem does not match those strengths. The clearest case is synchronous request-reply: if Rajesh's card swipe needs an instant fraud decision, a direct REST call is simpler and faster than publishing a request and correlating a reply through Kafka.
The second case is low volume, where Kafka's operational cost dwarfs the benefit. The third is rich messaging features like per-message priority that traditional brokers provide natively. The fourth is small teams without operational maturity, and the fifth is transactional relational work that belongs in a database.
In my National Bank of India work, I made exactly these calls. The card authorization path was synchronous REST. The handful of end-of-day emails went on a managed queue. A priority-sensitive wire path used a classic broker. And the transaction firehose that fed analytics, fraud, and the ledger with replay was the workload that genuinely belonged on Kafka.
Remember one sentence: Kafka is for high-throughput, replayable, multi-consumer streaming -- use a direct call for request-reply, a managed queue for low volume, and a traditional broker for priority routing.
---