skip to content

What do Subscription.request(n) and Subscription.cancel() do, and what are the rules around calling them?

level: middleimportance: must knowfreq 60%

answer

  1. request(n) = demand, additive/cumulative
  2. n<=0 -> onError(IllegalArgumentException)
  3. Long.MAX_VALUE = unbounded (default subscribe)
  4. cancel() = stop + release, idempotent, best-effort
  5. only the Subscriber calls them, they go upstream

basics

~20 s

request(n) tells the Publisher the Subscriber is ready to receive up to n more elements — it is the demand signal. cancel() tells the Publisher to stop emitting and release resources. Both are called by the Subscriber on its Subscription.

solid answer

~50 s

Subscription has two methods, both invoked by the Subscriber. request(long n) signals demand: it permits the Publisher to send up to n additional onNext elements, and demand is cumulative and additive across calls. n must be positive; requesting n <= 0 is a spec violation and the Publisher must respond with onError (IllegalArgumentException). request(Long.MAX_VALUE) means effectively unbounded demand (fast path, no flow control). cancel() asks the Publisher to stop sending and clean up; after cancel the Subscriber may still receive already-in-flight signals but should expect no new ones, and cancel is idempotent. Both request and cancel must be safe to call from within onNext/onSubscribe and are the Subscriber's only levers on the stream. This demand mechanism is exactly what Reactor builds its operators on; in Reactor you call them via BaseSubscriber.request/cancel or let operators manage them.

code

java · 19 lines
java
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Flux;

Flux.range(1, 100).subscribe(new BaseSubscriber<Integer>() {
    @Override protected void hookOnSubscribe(Subscription s) {
        request(2); // demand exactly 2 to start (NOT Long.MAX_VALUE)
    }
    @Override protected void hookOnNext(Integer value) {
        System.out.println("got " + value);
        if (value == 4) {
            cancel();      // stop early; idempotent, releases upstream
        } else {
            request(1);    // cumulative: ask for one more each time
        }
    }
});
// Prints got 1, got 2, got 3, got 4, then cancels — never reaches 100.
// request(0) or request(-1) here would instead trigger onError(IllegalArgumentException).

go deeper

for a junior

Knows request means 'ask for items' and cancel means 'stop'.

for a middle

Knows demand is cumulative, n>0 required, Long.MAX_VALUE = unbounded.

for a senior

Explains the n<=0 onError rule, cancel idempotency, and best-effort trailing signals.

for a principal

Connects request/cancel to how operators (take/limitRate) and WebFlux client-disconnect drive the stream.

`Subscription` is the private, one-to-one channel between a single Publisher and a single Subscriber, handed to the Subscriber in `onSubscribe`. It exposes exactly two methods, and **only the Subscriber calls them** — they flow *upstream*. **`request(long n)` — demand signaling:** - Tells the Publisher: 'you may now send me up to `n` more `onNext` elements.' Nothing is emitted until the Subscriber requests. - Demand is **additive/cumulative**: `request(3)` then `request(2)` yields an outstanding demand of 5. The Publisher tracks the running total and must never emit more `onNext` than has been requested. - **`n` must be > 0.** Calling `request(n)` with `n <= 0` is a specification violation (Rule 3.9); the Publisher must signal `onError` with an `IllegalArgumentException`. This is a favorite interview trap. - **`request(Long.MAX_VALUE)`** is treated as **effectively unbounded** demand — the Publisher may emit freely without further requests. Reactor's `subscribe()` with no explicit demand and operators like `.subscribe(consumer)` request `Long.MAX_VALUE` by default, which is why simple pipelines 'just run'. - `request` may be called re-entrantly (e.g., from inside `onNext`) — the spec requires the Publisher to tolerate this without unbounded recursion (Rule 3.3, bounded stack). **`cancel()` — teardown:** - Tells the Publisher the Subscriber wants no more elements and the Publisher should stop and release resources (close connections, cancel timers, etc.). - **Idempotent**: calling `cancel()` multiple times has no additional effect (Rule 3.7). - After `cancel()`, the Publisher should *eventually* stop, but because everything is asynchronous, a few already-dispatched `onNext` signals may still arrive; the Subscriber must tolerate this (best-effort, not instantaneous). - Cancellation is how operators like `take(n)`, `timeout`, and `next()` stop an upstream early, and how WebFlux aborts a stream when the HTTP client disconnects. **Relationship to the rest of the contract:** - `request`/`cancel` are the *upstream* half of the protocol; `onNext`/`onError`/`onComplete` are the *downstream* half. Together they form a push model bounded by pull-based demand. - Terminal signals (`onComplete`/`onError`) implicitly end the subscription — you don't cancel after a terminal. **Reactor usage:** Extend `reactor.core.publisher.BaseSubscriber` and call `request(n)`/`cancel()` (or `requestUnbounded()`); or rely on operators. `Flux.range(1, 100).take(3)` internally requests, receives 3, then cancels upstream. **Gotchas:** - Forgetting to call `request(n)` in a hand-rolled Subscriber means the stream hangs — nothing is ever emitted. - Requesting 0 or negative is not 'request nothing', it is an error condition. - `cancel()` is not a guarantee of immediate silence; design for a possible trailing element. - Note: detailed *backpressure strategies* (buffer/drop/latest/error on overflow) belong to the backpressure topic — here the focus is purely the `request(n)`/`cancel()` primitives the spec defines.

  • What happens if you call request(0) or request(-1)?
    It is a spec violation (Rule 3.9). The Publisher must respond by signaling onError with an IllegalArgumentException. It does not mean 'pause' or 'request nothing' — those semantics don't exist; you simply stop calling request to pause.
  • After cancel(), can the Subscriber still receive onNext?
    Possibly. Cancellation is asynchronous and best-effort, so signals already in flight may still be delivered. A correct Subscriber tolerates a few trailing onNext calls after cancel; it just should not expect any *new* work to start.
  • How does Reactor's default .subscribe(consumer) request demand?
    It requests Long.MAX_VALUE — unbounded demand — so the whole sequence runs without manual flow control. To exert flow control you supply a custom Subscriber (e.g., BaseSubscriber) or use operators like limitRate that re-window the requests.

saying these in an interview costs you the question

  • Saying request(0) pauses or requests nothing
  • Thinking the Publisher calls request/cancel
  • Believing demand is per-call, not cumulative
  • Assuming cancel() stops delivery instantly with zero trailing elements
  • Not knowing Long.MAX_VALUE means unbounded

context