What are PriorityChannel and RendezvousChannel, and when would you use each?
answer
- Priority = QueueChannel + PriorityBlockingQueue, 'priority' header
- Higher priority number dequeued first; sequence tiebreaker keeps FIFO for ties
- Rendezvous = QueueChannel with SynchronousQueue, zero capacity
- Rendezvous: send blocks until receive (direct handoff)
- Priority only matters when there is a backlog
basics
~20 sBoth are pollable point-to-point channels. PriorityChannel is a QueueChannel that orders buffered messages by priority instead of FIFO. RendezvousChannel is a zero-capacity queue where the sender blocks until a receiver takes the message (direct handoff).
solid answer
~40 sBoth extend the pollable/queue family. **PriorityChannel** behaves like a QueueChannel but is backed by a `PriorityBlockingQueue`, so messages come out in priority order rather than FIFO. By default it reads the `priority` message header (higher first); you can supply a custom `Comparator<Message<?>>` for other ordering. Use it when some messages must jump the queue (e.g. urgent commands ahead of bulk work). **RendezvousChannel** is a subclass of QueueChannel backed by a `SynchronousQueue`: it has zero capacity, so a `send()` blocks until a receiver calls `receive()` and vice versa — a direct, synchronous hand-off between two threads with no buffering. Use it when you need a producer and consumer to meet in lock-step, e.g. to guarantee a message isn't 'accepted' until someone is ready to take it. Both still require a polling consumer.
code
java · 27 lines@Configuration
public class PollableVariantsConfig {
// Urgent messages (higher 'priority' header) jump ahead of bulk ones
@Bean
public PollableChannel priority() {
return new PriorityChannel(1000); // bounded; default = 'priority' header
}
// Custom ordering example: by a numeric header 'slaTier'
@Bean
public PollableChannel prioritByTier() {
return new PriorityChannel(
Comparator.comparingInt(m -> (int) m.getHeaders().getOrDefault("slaTier", 0)));
}
// Zero-capacity synchronous handoff: send() blocks until a receiver takes it
@Bean
public PollableChannel handoff() {
return new RendezvousChannel();
}
}
// Producer setting priority:
// Message<String> m = MessageBuilder.withPayload("urgent")
// .setPriority(9) // sets the IntegrationMessageHeaderAccessor.PRIORITY header
// .build();go deeper
Recognize the names and that priority reorders while rendezvous is a direct handoff.
Explain the backing data structures (PriorityBlockingQueue, SynchronousQueue) and the priority header.
Discuss the FIFO tiebreaker, custom comparators, bounded-capacity back-pressure, and rendezvous blocking/timeout risks.
Weigh when reordering/lock-step semantics are worth the complexity vs external brokers; reason about starvation and stall failure modes.
**PriorityChannel** (`org.springframework.integration.channel.PriorityChannel`): - A `PollableChannel` in the QueueChannel family, but instead of FIFO it stores messages in a `PriorityBlockingQueue`. - Ordering: by default it uses the message's **priority header**. The header key is `IntegrationMessageHeaderAccessor.PRIORITY` (the string `"priority"`), an `Integer` where a higher value is dequeued first. Messages without a priority header are treated as lowest priority. - You can pass a custom `Comparator<Message<?>>` to the constructor for arbitrary ordering (e.g. by a business field, timestamp, or SLA class). Because `PriorityBlockingQueue` is not stable, Spring's implementation adds a sequence tiebreaker so equal-priority messages keep insertion (FIFO) order — important for fairness. - Capacity: can be bounded; when bounded and full, send back-pressure applies like a QueueChannel. - Use cases: mixed workloads where urgent/interactive messages must overtake batch/bulk messages sharing one consumer; SLA tiers; expedited retries. - Gotcha: priority only matters while messages are *buffered*. If your consumer keeps the queue near-empty (drains as fast as producers fill), reordering rarely kicks in. Priority helps precisely when there's a backlog. **RendezvousChannel** (`org.springframework.integration.channel.RendezvousChannel`): - A subclass of `QueueChannel` backed by a `java.util.concurrent.SynchronousQueue`, which has **zero capacity** — it holds no elements. Every `put` (send) must be matched by a `take` (receive). - Semantics: `send()` blocks until another thread calls `receive()`, and `receive()` blocks until a `send()` arrives. The two threads 'rendezvous' and hand the message off directly. This is a synchronization point, not a buffer. - Because it's still a PollableChannel, the receiving side is typically a polling consumer; the poll's `receive()` completes the rendezvous. - Use cases: strict producer/consumer lock-step where the producer must not proceed until the message is genuinely accepted (e.g. bounded in-flight of exactly one, request/reply-style hand-offs, throttling where you never want buffering). It's effectively QueueChannel with capacity 0 and blocking semantics. - Gotcha: because both sides block, a slow or absent consumer will block producers indefinitely unless send/receive timeouts are set — misuse causes stalls/deadlock-like behaviour. Also, it decouples threads but NOT time (no buffering), so it does not smooth bursts. **Where they sit:** Both are point-to-point and pollable. PriorityChannel = reordered buffer; RendezvousChannel = no buffer, synchronous handoff. Contrast with plain QueueChannel = FIFO buffer, and DirectChannel = no buffer, same-thread synchronous (no blocking rendezvous, because the handler is invoked inline rather than by a separate receiver).
- Two messages have the same priority in a PriorityChannel — what determines their order?They fall back to insertion order (FIFO). Spring adds a monotonically increasing sequence tiebreaker on top of the PriorityBlockingQueue (which is otherwise not stable) so equal-priority messages are dequeued in the order they were sent, avoiding starvation among peers.
- What risk does RendezvousChannel introduce that a QueueChannel does not?Blocking. Since it has zero capacity, send() blocks until a receiver is ready. A missing or slow consumer stalls producers indefinitely unless you configure send/receive timeouts. It gives lock-step hand-off but no burst absorption, so it can create backpressure that looks like a hang.
saying these in an interview costs you the question
- Thinking PriorityChannel reorders even when the queue is empty (only matters with backlog)
- Saying lower priority number is dequeued first (higher wins by default)
- Believing RendezvousChannel buffers messages (it has zero capacity)
- Assuming equal-priority messages are dequeued in random order (Spring keeps FIFO via a sequence tiebreaker)