skip to content

Explain application vs broker destination prefixes and how @MessageMapping / @SendTo route messages.

level: middleimportance: must knowfreq 68%

answer

  1. /app -> @MessageMapping controllers
  2. /topic /queue -> broker fan-out
  3. prefix stripped: /app/hello -> @MessageMapping("/hello")
  4. default return dest = /topic/<mapping>
  5. @SendToUser = per-principal /user/...

basics

~20 s

Application prefixes (like /app) route incoming client messages to your @MessageMapping controller methods. Broker prefixes (like /topic, /queue) go straight to the broker for subscription/fan-out. @SendTo names the broker destination the method's return value is published to.

solid answer

~40 s

Spring routes a STOMP message by matching its destination against the configured prefixes. `setApplicationDestinationPrefixes("/app")` means a client SEND to `/app/hello` is delivered to your `@MessageMapping("/hello")` method — these are messages you want server-side logic to handle. `enableSimpleBroker("/topic", "/queue")` means destinations starting with `/topic` or `/queue` are broker-managed: clients SUBSCRIBE there, and messages sent there are fanned out to subscribers without hitting a controller. Inside a `@MessageMapping` method, the return value is, by default, published to `/topic/<mappedName>`; `@SendTo("/topic/greetings")` overrides that target destination. Note the prefix asymmetry: the client sends to `/app/hello` but the controller mapping is just `/hello` — the application prefix is stripped before matching. This split keeps 'business logic' destinations and 'broadcast' destinations cleanly separated.

code

java · 18 lines
java
@Controller
public class ChatController {

    // client SENDs to /app/chat/{roomId}
    @MessageMapping("/chat/{roomId}")
    @SendTo("/topic/rooms/{roomId}")   // broadcast to that room's subscribers
    public ChatEvent onMessage(@DestinationVariable String roomId,
                               @Payload ChatMessage in,
                               Principal principal) {
        return new ChatEvent(principal.getName(), roomId, in.text());
    }

    @MessageExceptionHandler
    @SendToUser("/queue/errors")       // private error reply to sender
    public String onError(Exception e) {
        return "error: " + e.getMessage();
    }
}

go deeper

for a junior

Know /app = handled by controllers, /topic = broadcast to subscribers.

for a middle

Explain prefix stripping, default return destination, and @SendTo vs @SendToUser.

for a senior

Add @DestinationVariable, @SubscribeMapping request/reply, and MessageConverter serialization details.

for a principal

Design a destination namespace convention (topics vs queues vs user destinations) that scales across an external broker and multiple app instances.

### Two kinds of destinations Every STOMP frame carries a `destination` header (e.g. `/app/chat`, `/topic/room1`). Spring decides what to do with a message by matching that destination's **prefix**: **Application destination prefixes** — configured via `registry.setApplicationDestinationPrefixes("/app")`. Messages whose destination starts with `/app` are routed to the *annotation method handler*, i.e. your `@Controller` beans' `@MessageMapping` methods. These are destinations where you want to run server code (validate, persist, transform) before anything is broadcast. **Broker destination prefixes** — configured via `registry.enableSimpleBroker("/topic", "/queue")` (or `enableStompBrokerRelay(...)`). Messages/subscriptions whose destination starts with these prefixes are handled directly by the **broker**. Clients `SUBSCRIBE` to `/topic/...` to receive broadcasts; anything published there is fanned out. By convention `/topic` = one-to-many (pub/sub) and `/queue` = one-to-one (point-to-point), though the simple broker treats both similarly. ### The prefix-stripping detail (common gotcha) When a client sends to `/app/hello`, the application prefix `/app` is **stripped** before matching, so the controller mapping is `@MessageMapping("/hello")`, NOT `@MessageMapping("/app/hello")`. Getting this wrong is the classic 'my handler never fires' bug. ### @MessageMapping and @SendTo ```java @Controller public class GreetingController { @MessageMapping("/hello") // matches client SEND to /app/hello @SendTo("/topic/greetings") // return value published here public Greeting greet(HelloMessage msg) { return new Greeting("Hello, " + msg.getName()); } } ``` - `@MessageMapping` maps an inbound application destination to a handler method. - The method **return value** is serialized (via a `MessageConverter`, JSON by default with Jackson on the classpath) and sent to a broker destination. - **Default destination** without `@SendTo`: `/topic/<mapping>` (e.g. `/topic/hello`). `@SendTo` overrides this — you can even list multiple destinations. - `@SendToUser` sends to a user-specific destination (`/user/{username}/...`) resolved per authenticated principal — used for private replies. ### Method arguments Handler methods can accept: the payload (`@Payload`, or a plain object — default), `@Header`/`@Headers`, `@DestinationVariable` for templated destinations like `@MessageMapping("/chat/{roomId}")`, the `Principal`, and `Message<?>` / `SimpMessageHeaderAccessor`. ### Related annotations - `@SubscribeMapping` — handles a SUBSCRIBE frame and returns a one-time reply directly to that subscriber (request/reply style), bypassing the broker. - `@MessageExceptionHandler` — handles exceptions thrown in messaging methods, optionally with `@SendToUser` for error replies. ### Gotchas - Forgetting that `/app` is stripped in the mapping. - Sending to a broker prefix you didn't enable — the message is silently dropped/unroutable. - Assuming `@SendTo` goes back only to the sender — it goes to the destination, i.e. ALL subscribers. Use `@SendToUser` / `SimpMessagingTemplate.convertAndSendToUser` for private replies.

  • A client sends to /app/hello but the @MessageMapping value is /app/hello and nothing fires. Why?
    The application prefix /app is stripped before matching, so the mapping must be @MessageMapping("/hello"). Prefixing the mapping with /app makes it never match.
  • Where does a @MessageMapping return value go if there's no @SendTo?
    To the default broker destination /topic/<mapping>, e.g. a method mapped to /hello broadcasts to /topic/hello.
  • How do you reply privately to just the sending user instead of broadcasting?
    Use @SendToUser (or SimpMessagingTemplate.convertAndSendToUser) which targets /user/{principal}/... resolved to that user's session.

saying these in an interview costs you the question

  • Thinking @SendTo replies only to the sender rather than all subscribers of the destination
  • Writing @MessageMapping("/app/hello") and expecting /app/hello sends to match
  • Believing /topic messages are routed to controllers
  • Confusing the SUBSCRIBE destination with the SEND destination

context