How does a Supplier produce messages (polling vs reactive), and when do you use StreamBridge instead?
answer
- imperative Supplier = polled, default 1s
- reactive Supplier<Flux> = called once
- StreamBridge = ad-hoc event-driven send
- StreamBridge replaces channel.send(...)
- Supplier has only -out-0
basics
~20 sAn imperative Supplier is polled on a schedule (default every second) and each returned value is sent out. A reactive Supplier<Flux<T>> is invoked once and its stream drives output. For event-driven, ad-hoc sends not tied to a poll, use StreamBridge.
solid answer
~40 sA `Supplier<T>` is a source. If imperative (`Supplier<String>`), SCSt wraps it in a poller and invokes it repeatedly — default fixed delay 1s, tunable via `spring.integration.poller.*` or per-binding poller settings — sending each non-null return to `<name>-out-0`. If reactive (`Supplier<Flux<T>>`), it's invoked **once** at startup and the returned Flux is the continuous message source, so you control emission timing yourself. Polling is a poor fit for genuinely event-driven producers (an HTTP request, a DB trigger) because it's clock-driven and would emit even when there's nothing new. For those, inject `StreamBridge` and call `send(bindingName, payload)` imperatively from wherever the event occurs; StreamBridge can create bindings dynamically and is the functional replacement for the old `channel.send(...)`.
code
java · 29 lines@Configuration
public class Sources {
// Polled every 1s (default). Returns null to skip a poll.
@Bean
public Supplier<String> heartbeat() {
return () -> "alive-" + Instant.now();
}
// Invoked ONCE; the Flux is the message stream.
@Bean
public Supplier<Flux<Long>> ticks() {
return () -> Flux.interval(Duration.ofSeconds(5));
}
}
@Service
class OrderPublisher {
private final StreamBridge streamBridge;
OrderPublisher(StreamBridge streamBridge) { this.streamBridge = streamBridge; }
// Event-driven send, not tied to any poller
public void onOrder(Order order) {
streamBridge.send("orders-out-0", order);
}
}
// spring.cloud.function.definition=heartbeat;ticks
// spring.cloud.stream.bindings.heartbeat-out-0.destination=beats
// spring.integration.poller.fixed-delay=1000go deeper
Know a Supplier is a source and that StreamBridge sends messages manually.
Distinguish imperative (polled, 1s) from reactive (called once) suppliers and know StreamBridge exists for ad-hoc sends.
Tune poller settings, reason about reactive resubscription, and choose the right producer mechanism per use case.
Architect event-driven producers, handle dynamic-destination caching and resilience, and articulate the clock-driven-vs-event-driven trade-off across a fleet of services.
**Three ways to originate messages** in the functional model, each with different semantics: **1. Imperative `Supplier<T>` — polled.** SCSt/Spring Integration wraps the supplier in a **polling adapter**. By default it polls with a fixed delay of **1 second**, calling `get()` each time and publishing the returned value to `<name>-out-0`. Returning `null` emits nothing for that poll. Tune the poller globally with `spring.integration.poller.fixed-delay` / `.max-messages-per-poll`, or per binding via `spring.cloud.stream.bindings.<name>-out-0.producer.poller.*`. This suits pull-based sources (scrape a value, read a counter) but is clock-driven, not event-driven. ```java @Bean public Supplier<String> timeSource() { return () -> Instant.now().toString(); // polled every second } ``` **2. Reactive `Supplier<Flux<T>>` — invoked once.** The framework calls the supplier a **single time** at startup and subscribes to the returned `Flux`; every element the Flux emits becomes a message. You own the cadence (intervals, external event streams, backpressure). No poller is involved. ```java @Bean public Supplier<Flux<String>> reactiveSource() { return () -> Flux.interval(Duration.ofSeconds(1)).map(i -> "tick-" + i); } ``` **3. `StreamBridge` — imperative, event-driven, ad-hoc.** When production is triggered by an external event (an incoming HTTP request, a scheduled job, a domain event) rather than a poll, a Supplier is awkward — you'd have to bridge the event into a queue the Supplier drains. Instead inject `StreamBridge` and send directly: ```java @RestController class PublishController { private final StreamBridge streamBridge; PublishController(StreamBridge sb) { this.streamBridge = sb; } @PostMapping("/publish") void publish(@RequestBody String body) { streamBridge.send("words-out-0", body); } } ``` `StreamBridge` looks up the binding by name; if the binding isn't already defined it **creates it dynamically** (cached, capped by `spring.cloud.stream.dynamic-destination-cache-size`). You can send a `Message<?>` with headers, and target either a configured binding name or an arbitrary destination. It's the direct functional replacement for the legacy `source.output().send(...)`. **Choosing between them:** - Continuous/pull, simple → imperative Supplier (accept the 1s poll semantics). - Continuous/push with full timing control, backpressure, external reactive stream → reactive `Supplier<Flux<T>>`. - Sporadic, event-triggered, or triggered from request/handler code → `StreamBridge`. **Gotchas.** - Forgetting that an imperative Supplier fires **every second by default** can flood a topic; always confirm the poller config. - A reactive Supplier is subscribed once — if its Flux completes or errors, the source stops; use `repeat()`/`retry()` for resilience. - `StreamBridge.send` returns a boolean and is synchronous to the binder's send; it does not itself add async buffering. - Dynamic destinations from StreamBridge still need broker permissions; the cache governs their creation. - Suppliers don't have input bindings, so only `-out-0` exists.
- Why is an imperative Supplier a poor fit for publishing a message when an HTTP request arrives?An imperative Supplier is clock-driven — SCSt polls get() on a fixed schedule regardless of whether an event occurred. HTTP arrival is event-driven and sporadic, so you'd have to buffer requests for the poller to drain. StreamBridge lets you send inline from the request handler exactly when the event happens.
- A reactive Supplier's Flux errors after an hour and messages stop. How do you make it resilient?The supplier is subscribed only once, so a terminal error/complete stops the source permanently. Add reactive resilience operators — e.g. retry()/retryWhen() to resubscribe on error, or repeat() to resubscribe on completion — inside the returned Flux so the source keeps producing.
- How does StreamBridge handle a destination that has no pre-declared binding?It creates the binding dynamically on first send and caches it (bounded by spring.cloud.stream.dynamic-destination-cache-size), so you can publish to ad-hoc destinations without static configuration.
saying these in an interview costs you the question
- Thinking a reactive Supplier<Flux> is polled repeatedly like the imperative one
- Not knowing the default 1s poll cadence for imperative suppliers
- Using a Supplier for request-triggered sends instead of StreamBridge