ArchitectMind
  1. Home
  2. Tutorials
  3. Book 4 · Spring & Spring Boot
  4. Volume VII
  5. Messaging Systems
Book 4 · Volume VII — Spring Integration, Testing & Cloud-Native Development · Topic 1.3

Messaging Systems

Kafka, RabbitMQ, JMS and cloud messaging

9 min readStage 2 · Frameworks & design8 interview questions
Chapter
Chapter 1 — Spring Integration
Prerequisites
Topics 1.1–1.2
You will be able to
Compare the major messaging systems by model and guarantees, use Spring's templates and listeners for each, abstract brokers with Spring Cloud Stream, and choose a messaging system for a given workload
Covers
Kafka · RabbitMQ · JMS · Cloud Messaging
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally
  5. 4A. Deep Dive — Broker-Neutral Services with Spring Cloud Stream
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

A messaging system (message broker or event streaming platform) stores and delivers messages between producers and consumers, decoupling them in time and space. Spring provides consistent programming models for the major systems: a template for sending (KafkaTemplate, RabbitTemplate, JmsTemplate) and annotated listeners for receiving (@KafkaListener, @RabbitListener, @JmsListener), plus Spring Cloud Stream as a broker-neutral abstraction.

Messaging systems compared
Figure 1 — Messaging systems compared

2Why It Exists

Direct synchronous calls couple availability and throughput of services. Brokers absorb load spikes, let consumers be offline, enable fan-out to many consumers and provide delivery guarantees. Different workloads — high-volume event streams, task queues with priorities, enterprise transactional messaging, serverless pipelines — led to different broker designs, and Spring supports all of them with similar APIs.

Log versus queue
Figure 2 — Log versus queue

3Core Concepts

3.1 Kafka

AspectKafka
ModelTopics split into partitions (append-only logs), consumers track offsets
OrderingPer partition (per key)
RetentionTime/size based or compacted — replay possible
ScalingPartitions × consumer groups
SpringKafkaTemplate, @KafkaListener (Chapter 2)
Typical useEvent streaming, event-driven microservices, CDC, analytics pipelines
Table 1 — Kafka

3.2 RabbitMQ

RabbitMQ exchanges and queues
Figure 3 — RabbitMQ exchanges and queues
JAVA
@Configuration
class RabbitConfig {
    @Bean TopicExchange orders() { return new TopicExchange("orders"); }
    @Bean Queue billing() {
        return QueueBuilder.durable("billing").quorum()
                           .deadLetterExchange("orders.dlx").build();
    }
    @Bean Binding billingBinding() {
        return BindingBuilder.bind(billing()).to(orders()).with("order.*.created");
    }
}
 
@Component
class BillingListener {
    @RabbitListener(queues = "billing")
    void on(OrderCreated event) { billingService.invoice(event); }
}
 
rabbitTemplate.convertAndSend("orders", "order.eu.created", new OrderCreated(orderId));
AspectRabbitMQ
ModelExchanges (direct, topic, fanout, headers) route to queues
DeliveryPer-message acknowledgements, redelivery, DLX, TTL, priorities
Queue typesClassic, quorum (replicated, recommended), streams (log-like)
SpringSpring AMQP: RabbitTemplate, @RabbitListener
Typical useTask/work queues, command messaging, complex routing, RPC
Table 2 — RabbitMQ

3.3 JMS

JAVA
@JmsListener(destination = "payments.requests", concurrency = "3-10")
public void onPayment(PaymentRequest request) { payments.process(request); }
 
jmsTemplate.convertAndSend("payments.requests", request);
AspectJMS (Jakarta Messaging)
NatureA Java API standard, implemented by brokers (ActiveMQ Artemis, IBM MQ, …)
ModelQueues (point-to-point) and topics (pub-sub), durable subscriptions
TransactionsLocal transacted sessions; XA with JTA (legacy)
SpringJmsTemplate, @JmsListener, DefaultMessageListenerContainer
Typical useEnterprise and mainframe integration, existing MQ estates
Table 3 — JMS

3.4 Cloud messaging

ServiceModelSpring integration
AWS SQS / SNSQueues / pub-sub fan-outSpring Cloud AWS (SqsTemplate, @SqsListener)
AWS Kinesis / MSKStreams / managed KafkaKinesis binder / Spring Kafka
Azure Service Bus / Event HubsBroker with sessions/DLQ / streaming (Kafka-compatible)Spring Cloud Azure
Google Pub/SubGlobal pub-subSpring Cloud GCP (PubSubTemplate)
Table 4 — Cloud messaging services

4How It Works Internally

Common Spring messaging mechanics:

1. Templates convert objects to broker messages via MessageConverters (JSON typically), set headers/properties and send (synchronously or returning futures).

2. Listener containers own consumer connections and threads: they poll or receive pushes, convert messages, invoke the annotated method, and acknowledge/commit on success.

3. Error handling in containers retries, rejects/requeues or dead-letters failed messages according to configuration.

4. Transactions: JMS/AMQP containers can use local transactions; Kafka has its own transactional producer model (Chapter 2).

5. Spring Cloud Stream binds functional beans (Function, Consumer, Supplier) to destinations through binder implementations, hiding broker APIs.

4ADeep Dive — Broker-Neutral Services with Spring Cloud Stream

JAVA
@SpringBootApplication
class ShippingApplication {
    @Bean
    Function<OrderPaid, ShipmentRequested> ship(ShippingService shipping) {
        return shipping::requestShipment;                // pure business function
    }
}
YAML
spring:
  cloud:
    function:
      definition: ship
    stream:
      bindings:
        ship-in-0:
          destination: orders.paid
          group: shipping                          # consumer group / durable queue
        ship-out-0:
          destination: shipments.requested
      kafka:  # or rabbit: … (binder-specific tuning)
        binder:
          brokers: kafka:9092
Abstraction levelProsCons
Native clients (Kafka/AMQP APIs)Full controlBoilerplate, broker lock-in in code
Spring templates + listenersIdiomatic, full broker featuresBroker-specific code
Spring Cloud StreamPortable business code, consistent conventionsLowest common denominator; broker features via binder config
Spring IntegrationRich EIP flows over any transportComplexity for simple cases
Table 5 — Choosing the abstraction level

5APIs / Features / Syntax

SystemSendReceiveBoot starter
KafkaKafkaTemplate@KafkaListenerspring-boot-starter-kafka
RabbitMQRabbitTemplate@RabbitListenerspring-boot-starter-amqp
JMSJmsTemplate, JmsClient (Spring 7)@JmsListenerspring-boot-starter-artemis / provider starter
Cloud StreamStreamBridge, SupplierConsumer/Function beansspring-cloud-stream-binder-*
AWS SQSSqsTemplate@SqsListenerSpring Cloud AWS starters
Table 6 — Messaging APIs

6Practical Examples

6.1 Same event, three brokers

JAVA
kafkaTemplate.send("orders.created", order.id().toString(), event);  // key → partition
// exchange + routing key
rabbitTemplate.convertAndSend("orders", "order.eu.created", event);
jmsTemplate.convertAndSend("ORDERS.CREATED", event);                      // queue/topic

6.2 Choosing a messaging system

Choosing a messaging system
Figure 4 — Choosing a messaging system

6.3 Guarantees compared

PropertyKafkaRabbitMQ (quorum queues)JMS brokerSQS standard / FIFO
OrderingPer partitionPer queue (single consumer)Per queue (single consumer)None / per message group
DeliveryAt-least-once (EOS within Kafka)At-least-onceAt-least-once / transactedAt-least-once / exactly-once dedup window
ReplayYesNo (streams: yes)NoNo
RoutingTopic/partition onlyRich (exchanges)SelectorsFiltering via SNS
Table 7 — Semantics that shape designs

6.4 Operational comparison

AspectSelf-managedManaged
KafkaBrokers, KRaft, upgrades, storageMSK, Confluent Cloud, Event Hubs (Kafka API)
RabbitMQCluster, quorum queues, upgradesAmazon MQ, CloudAMQP
JMSMQ servers, HA pairsCloud MQ offerings
Cloud-native—SQS/SNS, Service Bus, Pub/Sub
Table 8 — Operating the broker

6.5 Testing messaging code

Use Testcontainers (KafkaContainer, RabbitMQContainer, Artemis images) with @ServiceConnection to run listeners against real brokers in integration tests (Chapter 5), and embedded Kafka (@EmbeddedKafka) for lightweight Kafka tests.

7Common Problems & Mistakes

  • Choosing Kafka for task queues needing per-message routing and priorities (or RabbitMQ for event replay).
  • Assuming exactly-once delivery end to end.
  • No dead-letter handling.
  • Ignoring ordering requirements when scaling consumers.
  • Huge messages instead of claim checks.
  • Broker as database (unbounded retention used as system of record without design).

Each of these mistakes has a recognisable production signature. Using Kafka as a task queue shows up as head-of-line blocking: one slow or poison record stalls its whole partition, because offsets are committed in order and there is no per-message requeue, so a DefaultErrorHandler with a DeadLetterPublishingRecoverer becomes mandatory. Assuming exactly-once delivery leads to duplicate payments or e-mails after a rebalance or redelivery, which idempotent consumers keyed on a message id would have absorbed. Missing dead-letter handling turns one malformed message into an endless retry loop that burns CPU and hides everything queued behind it; RabbitMQ quorum queues help by enforcing a delivery limit. Scaling consumers breaks ordering silently: raising concurrency on a RabbitMQ queue or a JMS listener lets messages for the same order be processed in parallel, so updates arrive out of sequence. Oversized messages hit broker limits, around 1 MB by default in Kafka, and slow down replication, which is why the claim-check pattern stores the payload in object storage and sends only a reference.

8Performance & Production Considerations

ConcernGuidance
ThroughputKafka for very high volume; batching and compression
LatencyRabbitMQ low per-message latency; Kafka tuned via linger/batch
DurabilityReplication (Kafka RF=3, quorum queues), persistent messages
MonitoringConsumer lag, queue depth, redeliveries, DLQ size
Table 9 — Production guidance

9Architecture & Design Considerations

  • Events vs commands: topics for facts, queues for work.
  • One broker technology per purpose, not per team.
  • Schema management (JSON Schema, Avro, Protobuf + registry).
  • Outbox pattern to publish reliably from database transactions.

10Interview Questions & Answers

Q1BasicConcept

What is the main difference between Kafka and RabbitMQ?

Kafka is a distributed, partitioned commit log, while RabbitMQ is a message broker built around queues. In Kafka, producers append records to topic partitions, records are retained by time or size whether or not anyone has read them, and each consumer group tracks its own offset, so consumers can replay history and many groups can read the same data independently. Ordering is guaranteed per partition, which in practice means per key. In RabbitMQ, producers publish to exchanges, which route messages to queues by binding rules; consumers acknowledge each message, after which it is deleted, and failures can be redelivered or dead-lettered. Kafka suits event streams and replay, RabbitMQ suits work distribution and flexible routing.

Q2BasicConcept

What is JMS?

JMS, now Jakarta Messaging 3.1, is a Java API standard rather than a broker. It defines how Java code connects, sends and receives through a ConnectionFactory, sessions, producers and consumers, with two destination types: queues for point-to-point delivery and topics for publish-subscribe, including durable subscriptions. Brokers such as ActiveMQ Artemis and IBM MQ implement the API, but their wire protocols differ, so JMS gives code portability, not interoperability between brokers. It supports local transacted sessions and, historically, XA transactions through JTA. Spring wraps it with JmsTemplate, the new fluent JmsClient in Framework 7, and @JmsListener with DefaultMessageListenerContainer. It remains common in banking and mainframe integration with existing MQ estates.

Q3IntermediateCode

How do you send and receive messages with Spring for RabbitMQ?

With Spring AMQP and Boot's spring-boot-starter-amqp, you send through RabbitTemplate, typically convertAndSend(exchange, routingKey, payload), which converts the object with the configured MessageConverter, usually a JSON converter, and publishes it to the exchange. You receive by annotating a bean method with @RabbitListener(queues = "billing"); a listener container consumes from the queue, converts the body to the method's parameter type, invokes the method and acknowledges on success. Topology such as a TopicExchange, a durable quorum Queue and a Binding is declared as beans, and RabbitAdmin creates it at start-up. For failures, configure retries and a dead-letter exchange, and enable publisher confirms when the producer must know that the broker accepted the message.

Q4IntermediateConcept

What is Spring Cloud Stream?

Spring Cloud Stream is a framework for event-driven microservices that binds plain functional beans to messaging destinations. You write a Supplier, Function or Consumer containing business logic, and the framework, through a binder for Kafka, RabbitMQ or a cloud service, connects its inputs and outputs to topics or exchanges named in configuration, such as ship-in-0 bound to orders.paid with a consumer group. It handles serialisation, consumer groups, partitioning, retries and dead-letter destinations consistently, and StreamBridge sends messages imperatively when needed. The benefit is uniform conventions and portable business code; the limit is that broker-specific behaviour still leaks through, so it does not remove the need to choose a broker deliberately.

Q5Senior EngineerTrade-off

When would you choose RabbitMQ over Kafka?

I choose RabbitMQ when the workload is distributing work rather than publishing a replayable stream of facts. Typical signs are task or command queues where each message is processed once and then discarded, per-message acknowledgement and redelivery so that one slow message does not block others, priorities, per-message or per-queue TTLs, delayed retries, and routing by topic patterns or headers through exchanges. RabbitMQ also offers low latency at moderate volume and supports request-reply patterns. Kafka wins when consumers need replay, several independent consumer groups, long retention, very high throughput or strict per-key ordering at scale. When both needs exist, many organisations run Kafka for events and RabbitMQ or SQS for work queues.

Q6Senior EngineerTricky

What delivery guarantee should you design for?

Design for at-least-once delivery with idempotent consumers. Every mainstream broker redelivers a message if the consumer crashes before acknowledging or committing, and producers retry when an acknowledgement is lost, so duplicates are normal rather than exceptional. At-most-once loses data, and exactly-once holds only inside specific boundaries: Kafka transactions give exactly-once for read-process-write within Kafka, and SQS FIFO queues deduplicate within a five-minute window. As soon as the consumer writes to a database or calls an external API, those guarantees end. So consumers record processed message ids or use upserts and natural keys, and producers use the outbox pattern so the database change and the event cannot diverge.

Q7Technical ArchitectTrade-off

What are the benefits of managed cloud messaging?

Managed services such as SQS and SNS, Amazon MSK, Azure Service Bus or Google Pub/Sub remove broker operations: no patching, cluster sizing, disk management or failover drills. They scale elastically, integrate with cloud identity, encryption and monitoring, and often bill per use, which suits spiky or small workloads. The costs are semantics and limits that differ from self-managed brokers, such as SQS standard queues having no ordering, message-size and throughput quotas, visibility-timeout behaviour, and provider-specific APIs that tie code to one cloud. Bills can also grow sharply at high volume. I accept those costs when operations capacity is the bottleneck, and keep application code behind Spring templates or Spring Cloud Stream to contain lock-in.

Q8Solution ArchitectDesign

How would you define a messaging strategy for an organisation?

I would start from purposes rather than products: event streaming for facts that many consumers need and may replay, and work queues for commands that are processed once. Pick one technology per purpose, often Kafka or a managed Kafka for events and RabbitMQ or a cloud queue for work, and publish platform conventions for topic and queue naming, partitioning keys, schemas in a registry with compatibility rules, security, retries and dead-letter handling. Mandate the outbox pattern for publishing from database transactions, and idempotent consumers. Provide an internal Spring starter that configures templates, listeners, tracing and consumer-lag metrics consistently. Finally, govern new broker requests through ADRs so the estate does not fragment team by team.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 4

Spring & Spring Boot

Volume I · Topic 1.1

Why Spring Exists

Spring Core, IoC & Dependency Injection

Volume I · Topic 1.2

Spring Framework Architecture

Spring Core, IoC & Dependency Injection

Volume I · Topic 1.3

Spring Modules

Spring Core, IoC & Dependency Injection

Volume II · Topic 1.1

Cross-Cutting Concerns

Spring AOP, Proxies, Events & Transactions

Volume II · Topic 1.2

Why Cross-Cutting Concerns Are Difficult

Spring AOP, Proxies, Events & Transactions

Volume II · Topic 1.3

Aspect-Oriented Programming

Spring AOP, Proxies, Events & Transactions

Volume III · Topic 1.1

Why Spring Boot Exists

Spring Boot, Configuration & Application Lifecycle

Volume III · Topic 1.2

Spring Boot Architecture

Spring Boot, Configuration & Application Lifecycle

Volume III · Topic 1.3

Spring Boot vs Spring Framework

Spring Boot, Configuration & Application Lifecycle

Volume IV · Topic 1.1

MVC

Spring MVC, REST & Web Applications

Volume IV · Topic 1.2

DispatcherServlet

Spring MVC, REST & Web Applications

Volume IV · Topic 1.3

MVC Request Lifecycle

Spring MVC, REST & Web Applications

Volume V · Topic 1.1

Repository Abstraction

Spring Data, JPA & Persistence

Volume V · Topic 1.2

Spring Data Architecture

Spring Data, JPA & Persistence

Volume V · Topic 1.3

Repository Types

Spring Data, JPA & Persistence

Volume VI · Topic 1.1

Security Architecture

Spring Security, OAuth2 & Identity

Volume VI · Topic 1.2

Security Filter Chain

Spring Security, OAuth2 & Identity

Volume VI · Topic 1.3

Security Context

Spring Security, OAuth2 & Identity

Volume VII · Topic 1.1

Enterprise Integration

Spring Integration, Testing & Cloud-Native Development

Volume VII · Topic 1.2

Integration Patterns

Spring Integration, Testing & Cloud-Native Development