Skip to content
All articles

Reactive Programming: From Netflix OSS to Project Reactor

Dean Jain

Dean Jain

Senior Staff Software Engineer · Enterprise AI, Data & Cloud Architect

· 5 min read

ReactiveProject ReactorJavaSpring
---
config:
  theme: neutral
  fontSize: 16
---
flowchart TB
    subgraph BLK["Blocking thread per request"]
        direction LR
        R1["Request"]:::warn --> T1["Thread blocks on I/O<br/>(idle, wasted)"]:::danger
    end
    subgraph RCT["Reactive non-blocking"]
        direction LR
        R2["Many requests"]:::obs --> T2["Few threads<br/>handle all via events"]:::good
    end
    classDef warn fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef danger fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111
    classDef obs fill:#DAE8FC,stroke:#333333,stroke-width:1px,color:#111111
    classDef good fill:#DCDCDC,stroke:#333333,stroke-width:1px,color:#111111

Figure 1: Why reactive exists blocking ties a thread to each request while it waits on I/O; reactive lets a few threads serve many requests via events.

Reactive programming gets a reputation for being complicated, and the Mono/Flux operators don’t help. But the reason it exists is simple: in the traditional model, every request grabs a thread and that thread blocks while waiting on I/O a database call, another service sitting idle but unavailable. At scale, you run out of threads long before you run out of CPU. Reactive flips this: a small pool of threads handles a flood of requests by reacting to events instead of waiting. The path from Netflix’s microservice toolkit to the Reactive Streams spec is the story of the industry learning to do more with fewer threads.

Summary

  • The point is non-blocking. Don’t tie up a thread waiting on I/O; let a few threads serve many requests by reacting to events. That’s how you scale.
  • Netflix OSS was the microservice precursor Eureka (discovery), Ribbon (client load balancing), Zuul (gateway), Hystrix (circuit breaker), Spring Cloud Config.
  • Reactive Streams is the standard four interfaces (Publisher, Subscriber, Subscription, Processor) with backpressure, so a fast producer can’t overwhelm a slow consumer.
  • Project Reactor implements it with Mono (0–1 elements) and Flux (0–N, even infinite), composed into pipelines.
  • Spring WebFlux brings it to the web non-blocking servers (Netty), WebClient, and reactive data access end to end.

1. The origin: Netflix OSS microservices

Before “reactive” was mainstream, Netflix open-sourced the toolkit that taught the industry how to run microservices resiliently and the problems it solved are exactly what reactive systems formalize. The pieces (bundled by Spring Cloud Netflix):

  • Eureka a discovery service: register a service and have its endpoints resolved dynamically (vs. hardcoded hosts).
  • Ribbon client-side load balancing across a service’s instances.
  • Zuul an API gateway: all external requests pass through, with filters for cross-cutting concerns.
  • Hystrix a circuit breaker: when a downstream service fails, trip the breaker and invoke a fallback instead of hanging or cascading the failure.
  • Spring Cloud Config externalized configuration from git.

The through-line: these tools all exist because services depend on other services over an unreliable network, and you must not let one slow or failed dependency take everything down. Hystrix’s circuit breaker is the most telling it’s an admission that failure is normal and must be designed for. That mindset expect failure, stay responsive, don’t block on dependencies is precisely what the Reactive Streams spec turned into a programming model.

2. The Reactive Streams spec and backpressure

The Reactive Streams spec standardizes async stream processing with non-blocking backpressure across four interfaces:

---
config:
  theme: neutral
  fontSize: 16
---
flowchart LR
    PUB["Publisher<br/>provides elements"]:::server -->|"onNext / onError / onComplete"|SUB["Subscriber<br/>consumes them"]:::obs
    SUB -.->|"request(n) backpressure"|PUB
    classDef server fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef obs fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111

Figure 2: The Reactive Streams contract the Subscriber pulls demand with `request(n)`, so a fast Publisher can't overwhelm a slow consumer (backpressure).

  • Publisher provides a potentially unbounded sequence of elements, publishing them according to the demand it receives.
  • Subscriber subscribes and consumes via onSubscribe, onNext, onError, onComplete.
  • Subscription the link between them, carrying request(n) (the subscriber signals how much it can handle) and cancel().
  • Processor both a Subscriber and a Publisher, for building stream stages.

The crucial concept is backpressure, encoded in request(n): the consumer tells the producer how much it’s ready for. Without it, a fast publisher floods a slow subscriber and you get memory blowups or dropped data. With it, the stream self-regulates the heart of why reactive systems stay stable under load. (A TCK ensures libraries are compatible with each other.)

Project Reactor is the implementation Spring adopted, exposing two Publisher types:

  • Mono produces at most one element (a single result or empty).
  • Flux produces zero, one, or many even an infinite stream.

You compose them with a fluent, declarative API (map, filter, flatMap, zip), and the pipeline has a lifecycle: assembly (publishers wired into a pipeline), subscription (subscribers attach), and runtime (events flow). Nothing happens until something subscribes reactive pipelines are lazy by design. (Java’s own CompletableFuture/CompletionStage brought the same fluent, async style to single results before Reactor generalized it to streams.)

3. The principles, and end-to-end reactive

The Reactive Manifesto distilled the why into principles that read like everything Netflix OSS was reaching for:

---
config:
  theme: neutral
  fontSize: 16
---
flowchart TD
    R["Reactive principles"]:::gov
    R --> P1["Stay responsive<br/>always answer in time"]:::good
    R --> P2["Embrace failure<br/>design for resilience"]:::server
    R --> P3["Assert autonomy<br/>components act independently"]:::obs
    R --> P4["⏳ Decouple time &amp; space<br/>async, over the network"]:::gate
    classDef gov fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef good fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111
    classDef server fill:#DAE8FC,stroke:#333333,stroke-width:1px,color:#111111
    classDef obs fill:#DCDCDC,stroke:#333333,stroke-width:1px,color:#111111
    classDef gate fill:#E8E8E8,stroke:#333333,stroke-width:1px,color:#111111

Figure 3: The reactive principles stay responsive, embrace failure, assert autonomy, and decouple in time and space.

Stay responsive under all conditions; accept uncertainty and build reliability on unreliable foundations; embrace failure by designing for resilience; assert autonomy so components act independently and collaborate; tailor consistency per component; and decouple time (process asynchronously) and space (embrace the network). These aren’t abstract backpressure is “stay responsive,” circuit breakers are “embrace failure,” and request(n) is “decouple time.”

To get the benefit, the whole path must be non-blocking one blocking call poisons the pipeline. Spring WebFlux delivers this end to end: non-blocking servers (Netty, Undertow), the non-blocking WebClient instead of RestTemplate, and reactive data access (ReactiveCrudRepository) over backends like Mongo, Redis, Cassandra, and Couchbase. Server-Sent Events and streaming response bodies let you push many messages per request and stream large payloads without tying up threads.

Why it matters: reactive programming isn’t about clever operators it’s about a resource model. Blocking ties a thread to every in-flight request; reactive frees those threads to do work while I/O is pending, so the same hardware serves far more load and degrades gracefully under pressure. Reach for it when you’re I/O-bound and scaling concurrency (high-fan-out gateways, streaming, many slow downstreams). It adds real cognitive cost, so don’t reactive-ify a simple CRUD app but when thread exhaustion is your ceiling, non-blocking with backpressure is how you raise it.

References