skip to content

How do you push messages to WebSocket clients from arbitrary server code (outside a @MessageMapping handler)?

level: middleimportance: should knowfreq 58%

answer

  1. Inject SimpMessagingTemplate anywhere
  2. convertAndSend = broadcast to /topic
  3. convertAndSendToUser = /user/{name}/...
  4. goes through brokerChannel like @SendTo
  5. must target an enabled broker prefix

basics

~20 s

Inject SimpMessagingTemplate and call convertAndSend(destination, payload) to broadcast to a topic, or convertAndSendToUser(user, destination, payload) to reach one user. This works from any bean — a service, scheduler, or event listener — not just message handlers.

solid answer

~40 s

@MessageMapping methods only fire in response to an incoming client message. To push server-initiated messages — from a REST controller, a @Scheduled job, a domain event listener, or a Kafka consumer — you inject SimpMessagingTemplate (auto-configured by @EnableWebSocketMessageBroker). Call convertAndSend("/topic/prices", quote) to fan out to all subscribers of that broker destination, or convertAndSendToUser(username, "/queue/notifications", payload) to deliver to a specific authenticated user across their sessions (Spring resolves it to /user/{username}/queue/notifications). The payload is serialized by the configured MessageConverter (Jackson JSON by default). The destination must start with a broker prefix you enabled; otherwise it's unroutable. This decouples message production from the WebSocket session — any bean can broadcast without holding a reference to sessions or channels.

code

java · 16 lines
java
@RestController
public class NotifyController {
    private final SimpMessagingTemplate messaging;
    NotifyController(SimpMessagingTemplate messaging) { this.messaging = messaging; }

    @PostMapping("/notify/{user}")
    public void notifyUser(@PathVariable String user, @RequestBody Alert alert) {
        // client subscribes to /user/queue/alerts; receives only its own
        messaging.convertAndSendToUser(user, "/queue/alerts", alert);
    }

    @PostMapping("/broadcast")
    public void broadcast(@RequestBody Banner banner) {
        messaging.convertAndSend("/topic/banners", banner);
    }
}

go deeper

for a junior

Know SimpMessagingTemplate lets any bean push to clients via convertAndSend.

for a middle

Distinguish convertAndSend vs convertAndSendToUser and know it goes to broker prefixes.

for a senior

Explain the brokerChannel path, MessagePostProcessor, user-destination resolution and Principal requirement.

for a principal

Account for multi-instance delivery (external broker) and message ordering/backpressure when fanning out high-volume server-initiated pushes.

### The problem it solves `@MessageMapping` handlers are *reactive to client input* — they run only when a client SENDs. But many push scenarios are *server-initiated*: a price tick arrives, a background job finishes, a domain event fires, another user posts to a chat. You need to publish to WebSocket subscribers from code that has no incoming message. ### SimpMessagingTemplate `@EnableWebSocketMessageBroker` auto-registers a `SimpMessagingTemplate` bean (an implementation of `SimpMessageSendingOperations`). Inject it anywhere: ```java @Service public class PriceService { private final SimpMessagingTemplate messaging; PriceService(SimpMessagingTemplate messaging) { this.messaging = messaging; } public void onTick(Quote q) { messaging.convertAndSend("/topic/prices/" + q.symbol(), q); } } ``` Key methods: - `convertAndSend(destination, payload)` — serialize payload and send to a broker destination; broadcast to all subscribers. - `convertAndSend(destination, payload, postProcessor)` — same, but mutate headers (e.g. add a custom STOMP header) via a `MessagePostProcessor`. - `convertAndSendToUser(user, destination, payload)` — send to a specific user. Spring prefixes it to `/user/{user}/{destination}` and resolves it against that user's active session(s) using the `UserDestinationResolver`. The client subscribes to `/user/queue/notifications` (no username) and only receives its own. ### How the message travels `convertAndSend` places the message on the `brokerChannel`. The broker (simple in-memory or the STOMP relay) then delivers it to matching subscriptions on the `clientOutboundChannel`. So it goes through exactly the same broker path as a `@SendTo` reply — the only difference is the trigger. ### Serialization The payload is converted by the registered `MessageConverter` chain. With Jackson on the classpath, POJOs become JSON and the `content-type` header is set. You can also pass a pre-built `Message<?>` if you need full control. ### Gotchas / edge cases - **Wrong prefix**: sending to `/app/...` or an unregistered prefix silently fails to reach clients. Always send to an enabled broker prefix (`/topic`, `/queue`). - **User resolution requires a Principal**: `convertAndSendToUser` needs the user to be authenticated on the WebSocket session (the STOMP CONNECT must establish a `Principal`). Anonymous sessions can't be targeted by username. - **Multiple sessions**: a user with two tabs has two sessions; `convertAndSendToUser` fans out to all of them by default. - **Ordering / threading**: sends are async through the broker channel; don't assume the client received it when the call returns. - **Scaling**: with the simple in-memory broker, only clients connected to *this* JVM receive the message. To reach clients on other instances you need an external broker relay (RabbitMQ/ActiveMQ) so all instances share subscriptions.

  • What's the difference between convertAndSend and convertAndSendToUser?
    convertAndSend broadcasts to all subscribers of a broker destination; convertAndSendToUser resolves to /user/{principal}/... and delivers only to the sessions of one authenticated user.
  • You call convertAndSend but with the simple broker clients on other app instances don't get it. Why?
    The simple broker is in-memory and per-JVM; subscriptions aren't shared across instances. You need an external STOMP broker relay so all instances route through a shared broker.
  • What does the client subscribe to for user-specific messages?
    It subscribes to /user/queue/... without its own name; Spring's UserDestinationResolver maps the server's /user/{name}/queue/... to that session.

saying these in an interview costs you the question

  • Thinking you can only push from @MessageMapping handlers
  • Sending to /app/... with SimpMessagingTemplate and expecting clients to receive it
  • Believing convertAndSendToUser works for anonymous (unauthenticated) sessions
  • Assuming the simple broker reaches clients on other instances

context