ArchitectMind
  1. Home
  2. Tutorials
  3. Book 3 · Jakarta EE & Enterprise Java
  4. Volume V
  5. Messaging Fundamentals
Book 3 · Volume V — Messaging, JSON, Security & Validation · Topic 1.1

Messaging Fundamentals

MOM, Producer, Consumer, Broker, Queue, Topic

9 min readStage 1 · Foundations8 interview questions
On this page
  1. 1. · Concept & Definition
  2. 2. · Why It Matters
  3. 3. · How It Works
  4. 4. · Java / Jakarta EE Code Examples
  5. Deep Dive — Messaging Versus Synchronous Calls
  6. Deep Dive — Commands and Events
  7. 5. · Configuration, Defaults & Diagnostics
  8. 6. · Production Usage Patterns
  9. 7. · Common Pitfalls & Misconceptions
  10. 8. · Best Practices
  11. 9. · Architect's Perspective & Trade-offs
  12. 10. · Interview Q&A

1· Concept & Definition

Message-oriented middleware (MOM) is infrastructure that accepts a message from a producer, stores it, and delivers it to one or more consumers. The broker is the server that does this. Messages are addressed to a destination, which is either a queue (each message goes to exactly one consumer) or a topic (each message goes to every subscriber). The defining property is that sender and receiver never interact directly.

What the broker adds between two systems
Figure 1.1.1 — What the broker adds between two systems

2· Why It Matters

  • It removes temporal coupling: the receiver may be restarting, redeploying or overloaded, and the sender is unaffected.
  • It levels load: a burst that would overwhelm a synchronous service becomes queue depth instead of errors.
  • It enables fan-out: adding a new consumer of an event requires no change to the producer.
  • It changes the failure model: instead of an immediate error the caller gets nothing, so progress must be tracked elsewhere.
  • It introduces duplicates, reordering and eventual consistency, which the consumer must handle (Chapter 2).
  • It adds a system to operate — storage, clustering, credentials, monitoring — that must be designed for, not assumed.

3· How It Works

MOM

PropertySynchronous call (HTTP/gRPC)Messaging
Availability requirementBoth sides up at the same timeOnly the broker
Back pressureErrors, timeouts, retries by the callerQueue depth, consumer pace
CouplingCaller knows the callee's address and contractBoth know a destination and a message contract
ResultReturned immediatelyArrives later, or via a reply message
Failure visibilityImmediate status codeDelayed; needs monitoring and dead-letter handling
Fan-outCaller calls each servicePublish once, any number of subscribers

Producer

A producer creates a message and sends it to a destination. It chooses the delivery mode (persistent or not), an optional time to live, a priority, and any properties used for routing or filtering. A send is complete when the broker acknowledges it — for persistent messages that means the message is on disk — so a producer must still handle send failures, and must decide what happens to the business operation if the send fails.

Consumer

Consumption styleAPICharacteristics
Message-driven bean@MessageDriven + onMessageContainer-managed, pooled, transactional — the Jakarta EE default
Asynchronous listenersetMessageListenerCallback per message; the application manages threads
Synchronous receivereceive() / receive(timeout)Blocking pull; simple, used in batch and tests
No-wait receivereceiveNoWait()Returns null when the queue is empty
Shared subscriptioncreateSharedConsumer / createSharedDurableConsumerSeveral instances share one topic subscription (JMS 2.0+)

Broker

Broker responsibilityWhat it means in production
Durable storageJournal or database; disk throughput bounds persistent send rate
RoutingQueues, topics, address settings, wildcard hierarchies
RedeliveryRetries with delay, delivery counters, dead-letter addresses
Flow controlProducer blocking or paging when memory limits are reached
ClusteringReplication or shared store for failover; client reconnection
SecurityAuthentication, per-destination authorisation, TLS

Queue and Topic

Point-to-point and publish/subscribe
Figure 1.1.2 — Point-to-point and publish/subscribe
AspectQueueTopic
DeliveryExactly one consumer receives each messageEvery subscriber receives a copy
ScalingAdd consumers to increase throughputAdd subscribers to add behaviour
RetentionMessage stays until consumed or expiredKept only for durable subscriptions
OrderingPer queue, broken by competing consumersPer subscription
Typical contentCommands and tasksEvents and notifications
CouplingSender expects the work to be donePublisher does not know the reactions
Who guarantees what
Figure 1.1.3 — Who guarantees what

4· Java / Jakarta EE Code Examples

Sending to a topic and to a queue
@ApplicationScoped
public class OrderEvents {
 
    // injected, container-managed
    @Inject JMSContext context;
 
    @Resource(lookup = "java:/jms/topic/orders.events")
    private Topic ordersEvents;
 
    @Resource(lookup = "java:/jms/queue/orders.commands")
    private Queue ordersCommands;
 
    public void publishPlaced(long orderId, String payload) {     // event → topic
        context.createProducer()
               .setDeliveryMode(DeliveryMode.PERSISTENT)
               .setProperty("eventType", "OrderPlaced")
               .setProperty("orderId", orderId)
               .send(ordersEvents, payload);
    }
 
    public void requestPicking(String payload) {                  // command → queue
        context.createProducer()
               .setTimeToLive(Duration.ofMinutes(10).toMillis())
               .send(ordersCommands, payload);
    }
}
A message-driven bean consuming the queue
@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType",
                              propertyValue = "jakarta.jms.Queue"),
    @ActivationConfigProperty(propertyName = "destinationLookup",
                              propertyValue = "java:/jms/queue/orders.commands"),
    @ActivationConfigProperty(propertyName = "maxSession", propertyValue = "10")
})
public class PickingListener implements MessageListener {
 
    @Inject PickingService picking;
 
    @Override
    // container transaction
    public void onMessage(Message message) {
        try {
            // JMS 2.0+, no casting
            String body = message.getBody(String.class);
            picking.handle(body);
        } catch (JMSException e) {
            // rollback → redelivery
            throw new EJBException(e);
        }
    }
}
Destination definitions in code
// Declaring destinations from the application, so deployment needs no manual setup
@JMSDestinationDefinition(
    name  = "java:/jms/queue/orders.commands",
    interfaceName = "jakarta.jms.Queue",
    destinationName = "ordersCommands")
@JMSDestinationDefinition(
    name  = "java:/jms/topic/orders.events",
    interfaceName = "jakarta.jms.Topic",
    destinationName = "ordersEvents")
@ApplicationScoped
public class MessagingConfig { }
Pulling a message synchronously
// Synchronous receive — batch jobs and tests
public Optional<String> drainOne() {
    try (JMSContext ctx = factory.createContext(JMSContext.AUTO_ACKNOWLEDGE)) {
        JMSConsumer consumer = ctx.createConsumer(ordersCommands);
        String body = consumer.receiveBody(String.class, 2_000);   // null after 2s
        return Optional.ofNullable(body);
    }
}
What the operator sees
# Broker view of the same destinations (Artemis CLI)
> queue stat
NAME  ADDRESS  CONSUMER_COUNT  MESSAGE_COUNT  MESSAGES_ADDED  DELIVERING
ordersCommands  ordersCommands  10  412  98,441  10
billing.ordersEvents  ordersEvents  1  0  98,441  0
DLQ  DLQ  0  37  37  0

Deep Dive — Messaging Versus Synchronous Calls

The choice is not stylistic. It follows from what the caller needs at the moment of the call.

QuestionIf yesIf no
Does the caller need the result to continue?Synchronous callMessage
Must the work survive the callee being down?MessageEither
Can the work be retried safely?Message with at-least-onceReconsider the design
Do several systems need to react?TopicQueue or call
Is the load spiky?Message (queue absorbs it)Either
Is an answer needed within a request timeout?Synchronous callMessage

A common hybrid: the request is handled synchronously up to the point where a durable decision is recorded, and everything that can happen afterwards — notifications, projections, downstream updates — travels as messages. The user gets an immediate answer, and the slow or unreliable parts no longer sit in the request path.

Deep Dive — Commands and Events

CommandEvent
MeaningDo thisThis happened
NamingImperative: ReservePickingPast tense: OrderPlaced
DestinationQueueTopic
ConsumersExactly one logical handlerAny number, unknown to the publisher
FailureSender expects the work to happenPublisher is unaffected
CouplingSender knows what should happenPublisher knows only what occurred
VersioningHandler and sender evolve togetherMust tolerate unknown consumers

Mixing the two is the most common modelling error in messaging systems: publishing SendInvoiceEmail as an "event" makes the publisher responsible for downstream behaviour, and consuming OrderPlaced as if it were a command makes every new subscriber a change to the producer's meaning.

5· Configuration, Defaults & Diagnostics

SettingWhereTypical value / note
Connection factoryServer resource or @JMSConnectionFactoryPooled; separate XA factory if JTA is required
maxSessionMDB activation configConsumer concurrency per instance
Delivery modeProducerPERSISTENT by default — decide per message type
Time to liveProducerBound staleness; avoids infinite backlogs
Address settingsBrokerMax size, paging, redelivery delay, DLQ address
Prefetch / consumer windowClient or brokerLarge values harm fairness across consumers
SymptomLikely cause
Queue depth grows steadilyConsumers slower than producers, or all failing
Messages consumed but nothing happensException swallowed in onMessage
No consumer receives a topic messageNon-durable subscriber was offline
JMSRuntimeException: connection failureBroker restart without client reconnection configured
Producer blocksBroker flow control — address size limit reached

6· Production Usage Patterns

  • Queues for commands, topics for events, with naming conventions that make the difference visible.
  • Destinations declared as deployment artefacts (definitions or infrastructure as code), not created by hand.
  • Message-driven beans for consumption, so transactions and pooling are container-managed.
  • A schema or contract per message type, versioned alongside the service.
  • Dashboards for depth, consumer count, delivery rate and dead-letter depth on every destination.

7· Common Pitfalls & Misconceptions

  • Using messaging where the caller must wait — adding latency and a correlation problem for nothing.
  • Publishing commands as events, which moves responsibility to the wrong side.
  • Assuming a topic keeps messages for subscribers that are offline.
  • Ignoring send failures, leaving the business operation committed but unannounced (see the outbox pattern, Volume IV Chapter 6).
  • No time to live, so a backlog of obsolete messages is processed hours later.
  • Treating the broker as a database, keeping messages as the record of state.

8· Best Practices

  • Model each message as a command or an event, and name it accordingly.
  • Make the payload self-describing and versioned; never send internal entities.
  • Set delivery mode and TTL deliberately for each destination.
  • Instrument depth and lag from day one — they are the early warning for everything else.

9· Architect's Perspective & Trade-offs

OptionCouplingLatencyFailure handlingFits
Synchronous HTTPTightLowestImmediate errorQueries, user-facing answers
Queue (command)LooseLow to mediumRetry, dead-letterWork that must happen once
Topic (event)LoosestLow to mediumPer subscriberNotifying many systems
Log-based stream (Kafka)LooseLowOffset replayHigh volume, replay, analytics

10· Interview Q&A

Q1Basic

What is message-oriented middleware?

Message-oriented middleware is infrastructure, usually a message broker, that sits between applications and transfers messages from producers to consumers. A producer sends a message to a named destination; the broker stores it reliably and delivers it to one or more consumers, possibly much later. Because the broker holds the message, the producer and consumer never need to be running at the same time, and neither needs to know the other's location. Examples include ActiveMQ Artemis, IBM MQ and RabbitMQ, typically used through Jakarta Messaging. This decoupling in time and location is the main reason for using messaging.

Q2Basic

What is the difference between a queue and a topic?

A queue implements point-to-point messaging: each message is delivered to exactly one consumer, even if several consumers are listening, so queues are used for work that must be done once, such as processing a payment. A topic implements publish-subscribe: each message is delivered to every current subscriber, so topics are used to broadcast facts that several systems care about, such as an order having been placed. Choosing between them is a design decision about who should act on a message. Many brokers also offer shared subscriptions, which combine a topic with several competing consumers per subscriber.

Q3Intermediate

What are competing consumers?

Competing consumers are several consumer instances reading from the same queue. The broker gives each message to only one of them, so they share the workload and throughput scales by adding consumers; if one fails, the others continue. For example, five instances of an invoice service can process invoice requests in parallel. The trade-off is ordering: messages are processed concurrently by different consumers, so a later message can finish before an earlier one. If order matters for related messages, such as updates to the same account, use message groups or partitioning so each group is handled by one consumer at a time.

Q4Intermediate

When should a message be a command rather than an event?

Use a command when the sender needs a specific action to be performed by a specific recipient, for example ChargeCard or SendInvoice. Commands are named in the imperative, go to a queue, are processed by exactly one consumer, and the sender usually cares about the outcome. Use an event when the sender is announcing that something has already happened, for example OrderPlaced or PaymentCaptured. Events are named in the past tense, are published to a topic, and the sender does not know or care who reacts. Mixing the two, such as an event that secretly expects one consumer to act, creates hidden coupling.

Q5Senior Engineer

What happens to topic messages published while a subscriber is offline?

It depends on the subscription type. With a non-durable subscription, the broker delivers messages only while the subscriber is connected; anything published while it is offline is simply never delivered to it. With a durable subscription, the broker remembers the subscription and stores messages for it while the subscriber is disconnected, delivering them when it reconnects. Shared durable subscriptions, from JMS 2.0, let several consumer instances share one durable subscription. For any subscriber that must not miss events, such as a service updating its own data from OrderPlaced events, durable subscriptions are essential, and their backlog must be monitored.

Q6Senior Engineer

How do you decide between messaging and a synchronous call?

Ask whether the caller needs the result to continue. If a user is waiting for a price quote or a stock check, call synchronously, with timeouts and fallbacks. If the work can complete later, such as sending a confirmation email or updating a search index, use messaging. Messaging is also the better choice when the work must survive the receiving system being down, when several systems need to react to the same fact, or when load spikes must be smoothed. The cost is eventual consistency and more complex error handling, so do not use messaging merely to avoid designing a good API.

Q7Technical Architect

What does introducing a broker cost an organisation?

A broker is a stateful, business-critical system that someone must operate. It needs durable storage, clustering and failover, backups, capacity planning and upgrades. It needs security: authentication, authorisation per destination and encryption. Teams need monitoring of queue depth, consumer lag and dead-letter queues, with alerts and runbooks for poison messages. Message formats need versioning and governance, because many systems depend on them. And debugging asynchronous flows requires correlation IDs and distributed tracing across producers and consumers. These costs are worth paying when decoupling is needed, but they should be planned and staffed from the start.

Q8Solution Architect

How does messaging change the consistency model of a system?

With synchronous calls inside one transaction, a change is either fully visible or not at all. With messaging, each system updates its own data when it processes a message, so the overall system becomes eventually consistent: for a while, the order may be placed but the stock not yet reserved. Intermediate states become visible to users and other services. Messages can arrive more than once or out of order, so consumers must be idempotent and tolerant of ordering. The design must define how long inconsistency may last, how users see pending states, and how reconciliation or compensating actions fix anything that goes wrong.

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.