skip to content

Explain the difference between event-driven and polling consumers in Spring Integration. When does each get used?

level: middleimportance: must knowfreq 32%

answer

  1. SubscribableChannel -> EventDrivenConsumer (push, subscribe)
  2. PollableChannel -> PollingConsumer (pull, needs Poller)
  3. DirectChannel = sender's thread; ExecutorChannel = pool
  4. poller mandatory on QueueChannel or startup fails
  5. channel type chooses the endpoint, not you

basics

~20 s

An event-driven consumer subscribes to a channel and is invoked immediately when a message is sent (push). A polling consumer periodically checks a channel for messages (pull). Which you get depends on whether the input channel is subscribable or pollable.

solid answer

~40 s

Spring Integration creates one of two endpoint types depending on the input channel's type. If the channel is a SubscribableChannel (e.g., DirectChannel, PublishSubscribeChannel, ExecutorChannel), the framework builds an EventDrivenConsumer: it subscribes the MessageHandler to the channel, so the moment a message is sent the handler runs — usually on the sender's own thread (DirectChannel) or a pooled thread (ExecutorChannel). If the channel is a PollableChannel (e.g., QueueChannel), it builds a PollingConsumer, which uses a Poller (Trigger + TaskScheduler) to call receive() on a schedule; a poller is mandatory here. Rule of thumb: event-driven is lower-latency and simpler; polling is used when messages are buffered/queued, when you need throttling (maxMessagesPerPoll), or when the source is inherently pull-based like a filesystem or JDBC inbound adapter.

code

java · 20 lines
java
@Configuration
public class Flow {

    // SubscribableChannel -> framework creates an EventDrivenConsumer
    @Bean
    public MessageChannel direct() { return new DirectChannel(); }

    // PollableChannel -> framework creates a PollingConsumer (needs a poller)
    @Bean
    public MessageChannel queued() { return new QueueChannel(100); }

    // Push: runs on the sender's thread the instant a message arrives
    @ServiceActivator(inputChannel = "direct")
    public void onEvent(String msg) { /* immediate */ }

    // Pull: scheduler drains up to 10 msgs every second
    @ServiceActivator(inputChannel = "queued",
        poller = @Poller(fixedDelay = "1000", maxMessagesPerPoll = "10"))
    public void onPoll(String msg) { /* scheduled */ }
}

go deeper

for a junior

Push vs pull; subscribable vs pollable channel.

for a middle

Name EventDrivenConsumer/PollingConsumer, the mandatory poller, and DirectChannel thread semantics.

for a senior

Discuss threading/transaction boundaries across DirectChannel/ExecutorChannel and maxMessagesPerPoll starvation.

for a principal

Weigh latency, back-pressure, transactional polling and pool sizing when designing the channel topology.

## The two consumer types Every endpoint that reads from a channel is one of two `AbstractEndpoint` subclasses, and Spring Integration **picks which one automatically based on the channel's interface**: ### 1. EventDrivenConsumer (push) - Used when the input channel implements **`SubscribableChannel`**: `DirectChannel`, `PublishSubscribeChannel`, `ExecutorChannel`. - On `start()`, it **subscribes** its `MessageHandler` to the channel via `channel.subscribe(handler)`. - When someone sends to the channel, the dispatcher **invokes the handler immediately** — no scheduling, no latency. - **Threading:** with a `DirectChannel`, the handler runs **on the sender's thread**, inside the sender's transaction — this is synchronous and single-hop. With an `ExecutorChannel`, dispatch hands off to a `TaskExecutor` thread pool (async, breaks the transaction boundary). ### 2. PollingConsumer (pull) - Used when the input channel implements **`PollableChannel`** (buffered), e.g., `QueueChannel`, `PriorityChannel`, `RendezvousChannel`. - It **cannot** react to sends; instead it uses a **`Poller`** — a combination of a **`Trigger`** (`PeriodicTrigger` for fixedDelay/fixedRate, or `CronTrigger`) and the `TaskScheduler` — to call `channel.receive(timeout)` repeatedly. - A poller is **required**: if none is configured (neither explicit `@Poller` nor a default poller bean), the context **fails to start**. - Key knobs (via `PollerMetadata`): `maxMessagesPerPoll` (how many messages to drain per poll cycle), `receiveTimeout`, `taskExecutor` (run polls on a pool), `transactionManager`/`adviceChain`, `errorHandler`. ## How the choice is made You do **not** pick the consumer type directly — you pick the **channel type**, and the framework selects the endpoint. Inbound channel adapters that are inherently pull-based (file, (S)FTP, JDBC, JPA, mail) are implemented as a related `SourcePollingChannelAdapter`, which is also poller-driven. ## Gotchas - People expect a `@ServiceActivator` to be async; on a `DirectChannel` it runs **synchronously on the caller's thread**. Latency and back-pressure live entirely with the sender. - Forgetting a poller on a `QueueChannel`-fed endpoint is a classic startup error: *"No poller has been defined for endpoint … and no default poller is available."* - With a `PollingConsumer`, `maxMessagesPerPoll = -1` means "keep receiving until the channel is empty within one poll" — a busy queue can starve the scheduler thread; bound it for fairness. - An `ExecutorChannel` looks event-driven but decouples threads, so exceptions no longer propagate back to the sender. ## When to use which - **Event-driven / SubscribableChannel:** default choice — low latency, simple, keeps transaction/thread context. Use `ExecutorChannel` when you deliberately want to fan work onto a pool. - **Polling / PollableChannel:** when you need buffering, throttling, scheduled/batched consumption, transactional polling, or the source is pull-based.

  • On which thread does a service activator run when its input is a DirectChannel?
    On the sender's (producer's) thread — DirectChannel dispatches synchronously within the caller's execution and transaction, so it's effectively a method call across the channel.
  • You feed a service activator from a QueueChannel and get a startup error about no poller. Why?
    A QueueChannel is pollable, so the endpoint is a PollingConsumer, which requires a Poller (Trigger + scheduler). With no @Poller and no default poller bean, the context cannot start the endpoint.

saying these in an interview costs you the question

  • Saying you choose EventDrivenConsumer vs PollingConsumer directly (you choose the channel type)
  • Claiming event-driven consumers are always asynchronous
  • Thinking a QueueChannel endpoint works without any poller
  • Assuming ExecutorChannel keeps the sender's transaction (it doesn't — it hands off to a pool)

context