Definition
The choice between Kafka and Solace depends on which core model matches your dominant requirement.
- Kafka – a durable, partitioned, append-only log that excels at long-term retention, deterministic replay, event sourcing, stream processing (Kafka Streams, ksqlDB), and horizontal scale by partition.
- Solace – a broker-centric message router whose strength is broker-native hierarchical topic routing with wildcard subscriptions, low-latency fan-out, and multi-protocol access (JMS, MQTT, AMQP, REST).
- Kafka ecosystem – Kafka Streams, ksqlDB, Kafka Connect, Schema Registry, and broad client support give Kafka a rich processing and integration platform.
- Solace routing – consumers express interest with wildcards, new routing needs are new subscription patterns, and no new topics are created.
- Complementary use – many banks run both: Solace for real-time selective distribution and Kafka as the durable replayable backbone.
Think of Kafka as National Bank of India's permanent ledger vault. Every transaction is written down in order and kept for years so you can replay the exact history, rebuild any report, and feed analytics. Think of Solace as the bank's smart internal mail room. It does not keep letters forever, but it is brilliant at instantly sorting each letter to exactly the desks that asked for that kind of letter, even as desks keep changing what they want. If your need is "remember everything and replay it," you want the vault. If your need is "deliver the right slice to the right desk right now," you want the mail room.
Understanding the Concepts
When should you choose Kafka?
Choose Kafka when the dominant requirement is durable streaming, replay, or high-throughput event processing.
Durable retention and replay. Kafka retains records on disk for a configured time (days, weeks, or indefinitely with compaction). Each consumer group tracks its own offset, so a consumer can rewind and replay from any point. This is what makes event sourcing possible: the log of events is the source of truth, and you rebuild state by replaying.
# Reset the settlement consumer group to replay all trade events
bin/kafka-consumer-groups.sh --bootstrap-server kafka-1:9092 \
--group settlement-group \
--topic bank.trade-events \
--reset-offsets --to-earliest --execute
Expected output:
GROUP TOPIC PARTITION NEW-OFFSET
settlement-group bank.trade-events 0 0
settlement-group bank.trade-events 1 0
settlement-group bank.trade-events 2 0
Stream processing ecosystem. Kafka Streams lets you write topologies that read topics, do joins, windowed aggregations, and stateful transforms, and write results back to Kafka with exactly-once semantics. ksqlDB lets you express the same logic with SQL.
package com.bank.kafka.streams;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.TimeWindows;
import org.apache.kafka.streams.kstream.Materialized;
import org.apache.kafka.common.serialization.Serdes;
import java.time.Duration;
import java.util.Properties;
/**
* Kafka Streams: count transactions per account in 5-minute windows.
* Pankaj uses this to detect suspicious activity at National Bank of India.
*/
public class FraudDetectionStream {
public static void main(String[] args) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "fraud-detection");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-1:9092");
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> transactions = builder.stream("bank.transactions");
transactions
.groupByKey()
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
.count(Materialized.as("txn-count-store"))
.toStream()
.filter((key, count) -> count > 10)
.to("bank.suspicious-accounts");
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
System.out.println("Fraud detection stream started.");
System.out.println("Accounts with >10 txns in 5 min -> bank.suspicious-accounts");
}
}
Expected output:
Fraud detection stream started.
Accounts with >10 txns in 5 min -> bank.suspicious-accounts
Partitioned horizontal scale with ordering. A Kafka topic is split into partitions, each an ordered log. All events for one account (keyed by account ID) stay ordered within a partition while the topic scales across brokers and consumers.
package com.bank.kafka;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.math.BigDecimal;
import java.util.Properties;
/**
* Rajesh produces transactions keyed by account ID.
* All events for SAV2001 go to the same partition, preserving order.
*/
public class PartitionedOrderProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "kafka-1:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "all");
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
String accountId = "SAV2001";
// All three events for SAV2001 go to the same partition
producer.send(new ProducerRecord<>("bank.transactions", accountId,
"{\"type\":\"CREDIT\",\"amount\":" + new BigDecimal("50000.00") + "}"));
producer.send(new ProducerRecord<>("bank.transactions", accountId,
"{\"type\":\"DEBIT\",\"amount\":" + new BigDecimal("10000.00") + "}"));
producer.send(new ProducerRecord<>("bank.transactions", accountId,
"{\"type\":\"CREDIT\",\"amount\":" + new BigDecimal("75000.00") + "}"));
System.out.println("3 events for " + accountId + " -> same partition, ordered");
}
}
}
Expected output:
3 events for SAV2001 -> same partition, ordered
When should you choose Solace?
Choose Solace when the dominant requirement is flexible, broker-side routing to heterogeneous consumers.
Diverse, changing routing needs. Solace lets consumers express interest with hierarchical wildcard subscriptions. When a desk changes what it wants, it adjusts its subscription pattern. No topics are created, no producers are modified.
package com.bank.solace;
import com.solacesystems.jcsmp.JCSMPFactory;
import com.solacesystems.jcsmp.JCSMPSession;
import com.solacesystems.jcsmp.JCSMPProperties;
import com.solacesystems.jcsmp.Topic;
/**
* Priya changes her subscription from all Mumbai to all India.
* No broker changes, no topic creation, no producer modification.
*/
public class DynamicSubscriptionChange {
public static void main(String[] args) throws Exception {
JCSMPProperties properties = new JCSMPProperties();
properties.setProperty(JCSMPProperties.HOST, "solace-broker:55555");
properties.setProperty(JCSMPProperties.VPN_NAME, "nbi-vpn");
properties.setProperty(JCSMPProperties.USERNAME, "priya");
JCSMPSession session = JCSMPFactory.onlyInstance().createSession(properties);
session.connect();
// Remove old subscription
Topic oldSub = JCSMPFactory.onlyInstance().createTopic("txn/IN/Mumbai/>");
session.removeSubscription(oldSub);
System.out.println("Removed: txn/IN/Mumbai/>");
// Add new subscription: all of India
Topic newSub = JCSMPFactory.onlyInstance().createTopic("txn/IN/>");
session.addSubscription(newSub);
System.out.println("Added: txn/IN/>");
System.out.println("Priya now receives all India transactions.");
System.out.println("No topics created. No producer changes.");
session.closeSession();
}
}
Expected output:
Removed: txn/IN/Mumbai/>
Added: txn/IN/>
Priya now receives all India transactions.
No topics created. No producer changes.
Multi-protocol access. Solace supports JMS, MQTT, AMQP, and REST in addition to its native protocol. This matters in mixed enterprise estates where different systems use different protocols.
Low-latency selective fan-out. Solace delivers only matching messages to each consumer, so consumers stay thin and receive data with low latency.
How do Kafka and Solace compare?
| Feature | Kafka | Solace |
|---|---|---|
| Core model | Durable append-only log | Broker-centric hierarchical router |
| Retention | Days, weeks, or indefinite | Persistence available but not primary strength |
| Replay | Yes, reset offset to any point | Not designed for long-horizon replay |
| Routing | Flat topics, consumer-side filtering | Broker-side wildcard matching |
| Throughput | Very high sequential throughput | Optimized for fan-out, not log throughput |
| Stream processing | Kafka Streams, ksqlDB | External processor needed |
| Protocol | Kafka binary protocol | JMS, MQTT, AMQP, REST, native |
| Topic creation | Explicit, pre-created | Publish-time string, no creation step |
| Consumer diversity | Same topic, same data to all | Wildcard patterns, each consumer gets its slice |
| Ecosystem | Broad: Connect, Schema Registry, clients | Enterprise: bridging, multi-protocol, guaranteed messaging |
package com.bank.comparison;
public class KafkaVsSolaceDecision {
public static void main(String[] args) {
System.out.println("Decision matrix:");
System.out.println();
System.out.println("Need durable retention + replay? -> Kafka");
System.out.println("Need event sourcing (log = truth)? -> Kafka");
System.out.println("Need stream processing? -> Kafka");
System.out.println("Need high sequential throughput? -> Kafka");
System.out.println();
System.out.println("Need diverse, changing routing? -> Solace");
System.out.println("Need broker-side wildcard filtering? -> Solace");
System.out.println("Need multi-protocol (JMS/MQTT/AMQP)? -> Solace");
System.out.println("Need low-latency selective fan-out? -> Solace");
System.out.println();
System.out.println("Need both durable storage + routing? -> Both!");
}
}
Expected output:
Decision matrix:
Need durable retention + replay? -> Kafka
Need event sourcing (log = truth)? -> Kafka
Need stream processing? -> Kafka
Need high sequential throughput? -> Kafka
Need diverse, changing routing? -> Solace
Need broker-side wildcard filtering? -> Solace
Need multi-protocol (JMS/MQTT/AMQP)? -> Solace
Need low-latency selective fan-out? -> Solace
Need both durable storage + routing? -> Both!
What are common mistakes in choosing?
Wrong: forcing Kafka to do fine-grained routing.
This causes topic proliferation. Creating hundreds of topics to match Solace-like routing leads to operational nightmares.
package com.bank.comparison;
public class WrongForceKafkaRouting {
public static void main(String[] args) {
System.out.println("WRONG: Forcing Kafka to route finely");
System.out.println(" Created 1800 topics for 3 types x 50 cities x 4 acct x 3 sizes");
System.out.println(" Result: partition explosion, ACL nightmare, days to add a city");
System.out.println();
System.out.println("CORRECT: Use Solace for fine-grained routing");
System.out.println(" One hierarchical namespace, wildcard subscriptions");
System.out.println(" Adding a city: publisher sends new topic string, done");
}
}
Expected output:
WRONG: Forcing Kafka to route finely
Created 1800 topics for 3 types x 50 cities x 4 acct x 3 sizes
Result: partition explosion, ACL nightmare, days to add a city
CORRECT: Use Solace for fine-grained routing
One hierarchical namespace, wildcard subscriptions
Adding a city: publisher sends new topic string, done
Wrong: forcing Solace to be a long-horizon replayable ledger.
Solace's persistence model is different from Kafka's partitioned log. Using Solace for seven-year compliance retention and full replay fights its design.
package com.bank.comparison;
public class WrongForceSolaceLedger {
public static void main(String[] args) {
System.out.println("WRONG: Using Solace as 7-year compliance ledger");
System.out.println(" Solace is not designed for long-horizon reprocessing");
System.out.println(" Cannot reset offset and replay months of events");
System.out.println();
System.out.println("CORRECT: Use Kafka for durable retention");
System.out.println(" retention.ms=220752000000 (7 years)");
System.out.println(" Reset consumer group to earliest and replay all");
}
}
Expected output:
WRONG: Using Solace as 7-year compliance ledger
Solace is not designed for long-horizon reprocessing
Cannot reset offset and replay months of events
CORRECT: Use Kafka for durable retention
retention.ms=220752000000 (7 years)
Reset consumer group to earliest and replay all
How are they used together in practice?
Many banks run both. Solace handles real-time selective routing to desks. Kafka acts as the durable event backbone for retention, analytics, and event sourcing. A bridge connects them so events flow through both.
package com.bank.architecture;
public class ComplementaryArchitecture {
public static void main(String[] args) {
System.out.println("=== National Bank of India Architecture ===");
System.out.println();
System.out.println("Solace (live routing):");
System.out.println(" Mumbai desk: txn/IN/Mumbai/>");
System.out.println(" Large txn monitor: txn/*/*/*/*/large");
System.out.println(" Brazil ops: txn/BR/>");
System.out.println(" Each desk gets only its slice, low latency");
System.out.println();
System.out.println("Kafka (durable backbone):");
System.out.println(" bank.transactions topic, 7-year retention");
System.out.println(" Analytics reads full stream");
System.out.println(" Compliance replays for audits");
System.out.println(" Kafka Streams computes fraud scores");
System.out.println();
System.out.println("Bridge: Solace -> Kafka Connect sink");
System.out.println(" Every routed event also lands in Kafka");
System.out.println(" Kafka is the system of record");
}
}
Expected output:
=== National Bank of India Architecture ===
Solace (live routing):
Mumbai desk: txn/IN/Mumbai/>
Large txn monitor: txn/*/*/*/*/large
Brazil ops: txn/BR/>
Each desk gets only its slice, low latency
Kafka (durable backbone):
bank.transactions topic, 7-year retention
Analytics reads full stream
Compliance replays for audits
Kafka Streams computes fraud scores
Bridge: Solace -> Kafka Connect sink
Every routed event also lands in Kafka
Kafka is the system of record
What about throughput and latency differences?
Kafka achieves very high throughput because its broker is simple: it appends to a sequential log and reads sequentially from disk. This design is optimized for batched, high-volume workloads. Solace achieves low per-message latency because the broker evaluates subscriptions and delivers directly to interested consumers.
The trade-off is that Kafka's simplicity gives it throughput at the cost of routing flexibility, while Solace's routing intelligence gives it flexibility at the cost of broker CPU.
package com.bank.comparison;
public class ThroughputVsLatency {
public static void main(String[] args) {
System.out.println("Kafka throughput:");
System.out.println(" Sequential disk I/O + batching");
System.out.println(" Millions of records/sec per cluster");
System.out.println(" Optimized for high-volume streaming");
System.out.println();
System.out.println("Solace latency:");
System.out.println(" Direct broker-to-consumer delivery");
System.out.println(" Sub-millisecond for matching + delivery");
System.out.println(" Optimized for selective fan-out");
System.out.println();
System.out.println("Choose based on dominant need:");
System.out.println(" High volume -> Kafka");
System.out.println(" Low latency selective delivery -> Solace");
}
}
Expected output:
Kafka throughput:
Sequential disk I/O + batching
Millions of records/sec per cluster
Optimized for high-volume streaming
Solace latency:
Direct broker-to-consumer delivery
Sub-millisecond for matching + delivery
Optimized for selective fan-out
Choose based on dominant need:
High volume -> Kafka
Low latency selective delivery -> Solace
What about the ecosystem difference?
Kafka has a much larger ecosystem. Kafka Streams and ksqlDB give native stream processing. Kafka Connect provides hundreds of connectors. Schema Registry manages Avro, Protobuf, and JSON schemas. The client library support spans every major language.
Solace has strong enterprise features: multi-protocol access, guaranteed messaging with persistent queues, and bridging capabilities, but it does not have an equivalent native stream-processing ecosystem.
package com.bank.comparison;
public class EcosystemComparison {
public static void main(String[] args) {
System.out.println("Kafka ecosystem:");
System.out.println(" - Kafka Streams (Java stream processing)");
System.out.println(" - ksqlDB (SQL stream processing)");
System.out.println(" - Kafka Connect (100+ connectors)");
System.out.println(" - Schema Registry (Avro, Protobuf, JSON)");
System.out.println(" - Clients: Java, Python, Go, .NET, Node.js, C/C++");
System.out.println();
System.out.println("Solace ecosystem:");
System.out.println(" - Multi-protocol: JMS, MQTT, AMQP, REST, native");
System.out.println(" - Guaranteed messaging with persistent queues");
System.out.println(" - Bridging (Solace-Kafka, Solace-Solace)");
System.out.println(" - Event Portal for design and governance");
System.out.println(" - No native stream processing (use external)");
}
}
Expected output:
Kafka ecosystem:
- Kafka Streams (Java stream processing)
- ksqlDB (SQL stream processing)
- Kafka Connect (100+ connectors)
- Schema Registry (Avro, Protobuf, JSON)
- Clients: Java, Python, Go, .NET, Node.js, C/C++
Solace ecosystem:
- Multi-protocol: JMS, MQTT, AMQP, REST, native
- Guaranteed messaging with persistent queues
- Bridging (Solace-Kafka, Solace-Solace)
- Event Portal for design and governance
- No native stream processing (use external)
What should a senior engineer know?
The senior insight is that these are complementary, not competing, and forcing one into the other's role creates problems.
- Forcing Kafka to route finely causes topic proliferation. Forcing Solace to be a seven-year replayable ledger fights its design.
- Pick one system of record (usually Kafka) so if the two diverge you know which is authoritative.
- The bridge between them is a first-class component: monitor it, because a stalled bridge breaks either durability or live delivery.
- Kafka's operational expertise is more widely available than Solace's, which matters for hiring and on-call.
package com.bank.comparison;
public class SeniorDecisionPoints {
public static void main(String[] args) {
System.out.println("Senior decision points:");
System.out.println("1. Match tool to dominant requirement");
System.out.println("2. Never force one tool into the other's role");
System.out.println("3. Pick one system of record (usually Kafka)");
System.out.println("4. Monitor the bridge as first-class infra");
System.out.println("5. Consider operational expertise availability");
System.out.println("6. Kafka ecosystem is larger and more mature");
System.out.println("7. Complementary > competitive");
}
}
Expected output:
Senior decision points:
1. Match tool to dominant requirement
2. Never force one tool into the other's role
3. Pick one system of record (usually Kafka)
4. Monitor the bridge as first-class infra
5. Consider operational expertise availability
6. Kafka ecosystem is larger and more mature
7. Complementary > competitive
Visual Overview
Decision Flow: Kafka vs Solace
What is the dominant requirement?
│
├── Durable retention + replay?
│ └── Kafka
│
├── Event sourcing (log = truth)?
│ └── Kafka
│
├── Stream processing (Streams/ksqlDB)?
│ └── Kafka
│
├── High sequential throughput?
│ └── Kafka
│
├── Diverse, changing routing needs?
│ └── Solace
│
├── Broker-side wildcard filtering?
│ └── Solace
│
├── Multi-protocol (JMS/MQTT/AMQP)?
│ └── Solace
│
└── Both durable storage + routing?
└── Both! (Bridge them)
Common Bank Architecture:
┌──────────────────┐ ┌──────────────────┐
│ Solace │ │ Kafka │
│ (live routing) │──────►│ (durable store) │
│ │ bridge│ │
│ Mumbai desk ◄─┐ │ │ Analytics group │
│ Brazil ops ◄─┤ │ │ Fraud group │
│ Large txn ◄─┘ │ │ Compliance replay │
└──────────────────┘ └──────────────────┘
Key Takeaway
The choice between Kafka and Solace comes down to which core model your dominant requirement needs. Kafka is a durable, partitioned, append-only log, so you choose it for long-term retention, deterministic replay, event sourcing, stream processing with Kafka Streams and ksqlDB, and high-throughput partitioned scaling. Solace is a broker-centric hierarchical router, so you choose it when consumers have diverse and frequently changing routing needs, when you want broker-side wildcard filtering without creating new topics, and when multi-protocol access and low-latency selective fan-out matter more than long-term replay.
At National Bank of India, Pankaj applied this split directly. Compliance needed seven-year trade retention and the ability to replay the entire history for audits, so that went to Kafka. Meanwhile, trading desks each wanted a different and often-changing slice of the live transaction feed without waiting for a platform team to create topics, so that went to Solace with wildcard subscriptions. A bridge captured every routed event into Kafka as the authoritative system of record.
The key senior insight is that these are complementary rather than competing. Forcing Kafka to route finely causes topic proliferation, and forcing Solace to be a long-horizon replayable ledger fights its design. Match the tool to the dominant requirement and let them work together.
Remember one sentence: Kafka stores and replays the event history; Solace routes live events flexibly to the right consumers; and in a bank you usually run both.
---