How does RabbitTemplate implement request/reply RPC with sendAndReceive over reply-to?
answer
- reply-to + correlation-id properties
- convertSendAndReceive blocks until reply or replyTimeout (~5s)
- direct reply-to = amq.rabbitmq.reply-to pseudo-queue, no manual correlation
- @RabbitListener return value auto-published to replyTo
- RPC = synchronous coupling, use sparingly
basics
~20 ssendAndReceive (and convertSendAndReceive) sends a request and blocks waiting for a reply. RabbitTemplate sets a reply-to queue and a correlation id, the server replies to that queue, and the template matches the reply back to the caller.
solid answer
~40 sRabbitTemplate supports synchronous request/reply RPC. convertSendAndReceive(exchange, routingKey, request) publishes the request with two key properties: replyTo (a reply queue) and correlationId. The calling thread blocks on a future until a reply arrives or the replyTimeout elapses (default ~5s), then the reply is converted back to a POJO. Modern Spring AMQP uses 'direct reply-to' — the pseudo-queue amq.rabbitmq.reply-to — which needs no dedicated reply queue or manual correlation; the broker routes the reply straight back over the same channel. Alternatively you configure a fixed reply queue plus a reply container. The server side is typically a @RabbitListener whose method returns a value; Spring automatically publishes that return value to the request's replyTo with the matching correlationId. Use RPC sparingly — it reintroduces synchronous coupling and blocking into an async system.
code
java · 24 lines// Client: blocking request/reply
@Service
public class PricingClient {
private final RabbitTemplate rabbitTemplate;
public PricingClient(RabbitTemplate t) { this.rabbitTemplate = t; }
public PriceResponse quote(PriceRequest req) {
// Uses direct reply-to by default; blocks up to replyTimeout.
Object reply = rabbitTemplate.convertSendAndReceive(
"pricing.exchange", "price.request", req);
if (reply == null) throw new IllegalStateException("pricing RPC timed out");
return (PriceResponse) reply;
}
}
// Server: returning a value auto-replies to replyTo with correlationId
@Component
public class PricingServer {
@RabbitListener(queues = "price.request.queue")
public PriceResponse handle(PriceRequest req) {
return new PriceResponse(req.sku(), computePrice(req));
}
private BigDecimal computePrice(PriceRequest r) { return BigDecimal.TEN; }
}go deeper
Know that RPC means sending a request and waiting for a reply, using reply-to and correlation-id.
Explain convertSendAndReceive blocking semantics, replyTimeout, and that a @RabbitListener return value becomes the reply.
Contrast direct reply-to vs fixed reply queue, correlation matching, and timeout/error handling.
Judge when synchronous RPC is justified vs async choreography, and the throughput/thread-pool implications and failure modes.
**The pattern:** normal messaging is one-way (fire-and-forget). Sometimes a caller needs a *response* — this is the **request/reply** (RPC) pattern, standardized in AMQP via two message properties: **`reply-to`** (the queue where the responder should send its answer) and **`correlation-id`** (a token the caller uses to match a reply to its original request, since many requests may be in flight). **Template methods:** `sendAndReceive(exchange, routingKey, Message)` works with raw Messages; `convertSendAndReceive(exchange, routingKey, Object)` adds MessageConverter marshalling on both request and reply, returning a POJO. There's also `convertSendAndReceiveAsType(...)` returning a `ParameterizedTypeReference` for generics. All are **blocking**: the calling thread waits until a reply arrives or `replyTimeout` (default 5000ms) expires — on timeout the convert* methods return `null` (or the send* returns null Message), which callers must handle. **How the reply is received — two mechanisms:** 1. **Direct reply-to (default, recommended):** the template uses RabbitMQ's built-in pseudo-queue **`amq.rabbitmq.reply-to`**. The client publishes with `reply-to = amq.rabbitmq.reply-to` and consumes the reply on the *same channel*; the broker routes the response directly with no real queue and no manual correlation-id bookkeeping needed. This is efficient (no per-request queue creation) and is the default when no reply container/queue is configured. 2. **Fixed reply queue + reply container:** you declare a dedicated reply queue and attach a `SimpleMessageListenerContainer` (or set `setReplyAddress` + a reply container on the template). Here the template generates a `correlationId`, stores a pending future keyed by it, and when a reply arrives on the reply queue it matches the `correlationId` back to the waiting caller. Older/temporary-queue-per-request approaches also exist but are less efficient. **Server (responder) side:** typically a `@RabbitListener`-annotated method that **returns a value**. Spring AMQP's listener adapter automatically takes the return value, converts it, and publishes it to the inbound message's `replyTo` address with the same `correlationId`. You don't write the reply publish yourself. If you need the reply to go elsewhere, `@SendTo("exchange/routingKey")` overrides the destination. If the listener method returns `void`, no reply is sent (the caller will time out). **Correlation & threading:** RabbitTemplate is thread-safe; multiple threads can issue concurrent RPCs. With direct reply-to each request's reply is tied to its channel; with a shared reply queue the correlationId disambiguates. The template maintains a map of outstanding requests to `CompletableFuture`/`PendingReply` objects. **Gotchas & when to use:** (1) **Blocking** — RPC turns your event-driven system partially synchronous; the caller thread is held for the round trip, so under load you can exhaust thread pools. (2) **Timeouts** — always handle the null/timeout case; a slow or dead responder makes callers stall then fail. (3) **No built-in retry** — a lost reply just times out. (4) Direct reply-to replies are **not durable** and the consumer must be actively consuming — fine for RPC by design. (5) Prefer true asynchronous choreography (separate response events/callbacks) for high-throughput paths; reserve sendAndReceive for genuinely synchronous needs like a query that a caller must wait on. (6) Ensure request and reply use compatible MessageConverters on both ends.
- What is 'direct reply-to' and why is it preferred over a temporary reply queue per request?It's RabbitMQ's built-in pseudo-queue amq.rabbitmq.reply-to; the client consumes the reply on the same channel with no real queue and no manual correlation handling. It avoids the overhead of declaring/deleting a queue for every request.
- What happens on the client if the server's @RabbitListener method returns void?No reply message is published, so the client's convertSendAndReceive blocks until replyTimeout and then returns null. The caller must treat that as a timeout/failure.
- Why is heavy use of sendAndReceive discouraged in an event-driven system?It's synchronous and blocking — it holds the caller thread for the full round trip, reintroduces temporal coupling, and can exhaust thread pools under load, negating the decoupling benefits of messaging.
saying these in an interview costs you the question
- Thinking sendAndReceive is asynchronous/non-blocking
- Assuming you must manually create a reply queue and set correlationId (direct reply-to handles it)
- Forgetting to handle the null timeout result
- Believing the server must manually publish the reply (the listener return value does it)