skip to content

Compare the in-memory simple broker with the StompBrokerRelay (RabbitMQ/ActiveMQ). When and why switch?

level: seniorimportance: should knowfreq 55%

answer

  1. simple = in-memory, one JVM, no durability
  2. relay = TCP to RabbitMQ/ActiveMQ (STOMP plugin, 61613)
  3. relay shares subscriptions across instances
  4. switch when scaling horizontally / need durability
  5. relay uses a 'system' connection for app sends

basics

~20 s

The simple broker keeps subscriptions and does fan-out in the app's memory — easy but single-instance and non-durable. The StompBrokerRelay forwards STOMP frames to a real broker (RabbitMQ/ActiveMQ), so multiple app instances share subscriptions and you get scaling and reliability.

solid answer

~50 s

enableSimpleBroker("/topic","/queue") uses Spring's built-in in-memory broker: it tracks subscriptions and matches/fans out messages inside the JVM. It's zero-infrastructure and fine for a single instance or dev, but subscriptions live only in that process, so two app instances can't see each other's subscribers, and there's no persistence, acknowledgements, or advanced routing. enableStompBrokerRelay("/topic","/queue") instead opens a TCP STOMP connection to an external broker (RabbitMQ with the STOMP plugin, or ActiveMQ) and forwards all broker-destined frames to it. Now every app instance is just a relay to the shared broker, so a message published on instance A reaches subscribers connected to instance B, and you inherit the broker's clustering, flow control, and durability. You switch when you scale horizontally, need reliability/durability, or want to integrate WebSocket traffic with other messaging. The tradeoff is operating and securing that broker.

code

java · 14 lines
java
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.setApplicationDestinationPrefixes("/app");

    // Single node / dev:
    // registry.enableSimpleBroker("/topic", "/queue");

    // Horizontally scaled / durable — relay to RabbitMQ (STOMP plugin):
    registry.enableStompBrokerRelay("/topic", "/queue")
            .setRelayHost("rabbitmq.internal")
            .setRelayPort(61613)
            .setClientLogin("app").setClientPasscode("secret")
            .setSystemLogin("app").setSystemPasscode("secret");
}

go deeper

for a junior

Know simple broker = in-memory/dev, relay = real external broker for production.

for a middle

Explain why the simple broker fails across instances and what enableStompBrokerRelay does.

for a senior

Detail the relay's system vs client connections, credentials, durability/backpressure, and the config-only migration.

for a principal

Reason about broker sizing (per-client TCP connections), HA/clustering, failure modes when the relay disconnects, and integration with the broader messaging fabric.

### Simple broker (`enableSimpleBroker`) `registry.enableSimpleBroker("/topic", "/queue")` activates `SimpleBrokerMessageHandler`, an **in-memory, in-process** broker. It maintains a registry of `{destination -> subscriptions}` and, when a message arrives on a broker destination, matches subscribers and writes to the `clientOutboundChannel`. Characteristics: - **Zero infrastructure** — nothing to install; great for dev, demos, single-node apps. - **Simple destination matching** — supports basic path patterns (with `AntPathMatcher`), but not the full routing semantics of a real broker. - **No persistence / no acks / no transactions** — messages are transient; if no subscriber is connected, they're gone. - **Single JVM only** — subscriptions are local. With N app instances behind a load balancer, a publish only reaches subscribers on the *same* instance. This is the #1 reason it doesn't scale. - Optional heartbeat via a `TaskScheduler` (`setTaskScheduler`). ### STOMP broker relay (`enableStompBrokerRelay`) `registry.enableStompBrokerRelay("/topic", "/queue").setRelayHost(...).setRelayPort(61613)....` activates `StompBrokerRelayMessageHandler`. The Spring app **does not do broker work itself** — it maintains a TCP STOMP connection to a real message broker and forwards: - client SUBSCRIBE/UNSUBSCRIBE frames to the broker, - messages published to broker destinations to the broker, and relays broker MESSAGE frames back to the right client sessions. Requirements: an external broker that speaks STOMP — **RabbitMQ** (enable the `rabbitmq_stomp` plugin, default port 61613) or **ActiveMQ**. You configure host, port, and login/passcode for both the *client* connections and the app's *system* connection (`setSystemLogin/Passcode`, `setClientLogin/Passcode`). Why it's better at scale: - **Shared subscription registry** — all app instances connect to the same broker, so a message published anywhere reaches every subscriber regardless of which instance they're connected to. This is what makes horizontal scaling correct. - **Reliability** — the broker provides durability, acknowledgements, flow control/backpressure, and clustering. - **Full routing** — real topic/queue semantics, and interop with other producers/consumers (e.g. backend services publishing to the same topics). ### The `/user/**` and system connection detail The relay uses a dedicated **'system' TCP connection** to the broker for the application's own sends (e.g. `SimpMessagingTemplate`, `@SendTo`) and to receive user-destination messages. Each connected client also gets its own TCP connection to the broker. Under high connection counts this matters for broker sizing. ### Choosing / migration - Start with the simple broker for a single node or early development. - Switch to the relay when you: run more than one instance, need message durability/reliability, must integrate WebSocket topics with other messaging systems, or need broker-grade flow control. - Migration is mostly a config change (`enableSimpleBroker` -> `enableStompBrokerRelay`) plus standing up and securing the broker; destination names and controller code stay the same. ### Gotchas - With the simple broker, a multi-instance deployment appears to work but silently drops cross-instance delivery — a subtle production bug. - The relay needs network reachability and credentials to the broker; a dropped relay connection stops all broadcasts until reconnect. - RabbitMQ requires the STOMP plugin explicitly enabled; forgetting it is a common setup error. - `spring-messaging` needs the `reactor-netty`/TCP client dependency for the relay's TCP connection.

  • Your chat works locally but in prod with 3 instances users miss messages from users on other pods. What's wrong?
    You're using the in-memory simple broker, whose subscriptions are per-JVM. Switch to enableStompBrokerRelay so all instances share a common external broker.
  • What must you enable on RabbitMQ to use it as a STOMP relay?
    The rabbitmq_stomp plugin (listening on 61613 by default); then point enableStompBrokerRelay at that host/port with client and system credentials.
  • Does switching from simple broker to relay change your @MessageMapping/@SendTo code?
    No. Destinations and controller code stay the same; it's a broker configuration change plus standing up the external broker.

saying these in an interview costs you the question

  • Deploying the in-memory simple broker behind a multi-instance load balancer and expecting cross-instance delivery
  • Thinking the simple broker persists undelivered messages
  • Believing the relay makes Spring itself the broker rather than a forwarder
  • Forgetting RabbitMQ needs the STOMP plugin

context