Two different things get called reactive in software, and plenty of articles treat them as one. A style of writing code inside a service is one. A way of building the whole distributed system around that service is the other. The Reactive Manifesto describes the second.
Version 2.0 of the manifesto, published on September 16, 2014, defines reactive architecture through four properties: responsive, resilient, elastic and message-driven. A system with those properties keeps responding under heavy load and when parts of it fail, because its components pass asynchronous messages, scale with demand and contain their own failures. The manifesto names no framework, language or message broker.
RxJava and Project Reactor belong to the code side. The Lightbend white paper by Jonas Bonér and Viktor Klang classes reactive programming as a subset of reactive systems, a code-level technique and not a system design.
What Is Reactive Architecture?

It works as a design discipline. The manifesto lists properties a system has to show, and it prescribes no framework, language or message broker. Done right, the system stays responsive under heavy load and partial failure because its components talk through asynchronous messages, scale with demand and contain their own failures.
That puts it at the level of software architecture decisions, such as where components draw their boundaries and what happens when one of them stalls.
Jonas Bonér wrote version 1.0 in 2013, and version 2.0 followed on September 16, 2014 (reactivemanifesto.org). Early contributors included Erik Meijer, Martin Odersky, Greg Young, Martin Thompson and Roland Kuhn (Lightbend blog). Explainers that date the manifesto to 2014 are counting version 2.0.
It also gets confused with specific tools. Akka and Spring WebFlux are frameworks, and RxJava operator chains are a coding style, so neither one is the architecture itself. No particular broker is required, Kafka included, and actors are optional.
The style does not depend on microservices either. A single-process actor system can meet all four properties.
The problems it goes after are slow dependencies, traffic spikes, cascading failures, exhausted thread pools and queues that grow without limit.
What Are the Four Traits of a Reactive System?

Responsive is the goal. Resilient, elastic and message-driven are how a system gets there.
| Trait | What the system promises | What delivers it |
|---|---|---|
| Responsive | Fast, consistent response times | Early problem detection and handling |
| Resilient | Stays responsive when components fail | Replication, containment, isolation, delegation |
| Elastic | Stays responsive when load rises or falls | Sharding, replication, no central bottlenecks |
| Message-driven | Loose coupling between components | Asynchronous message passing, backpressure |
Responsive does not mean fast on average. The manifesto asks for a reliable upper bound on response time, because predictable latency keeps error handling simple and users confident (Reactive Manifesto 2.0, 2014).
Resilience reaches beyond high availability setups. According to the manifesto, any system that is not resilient loses responsiveness after a failure, mission critical or not.
Elasticity runs in both directions, with resources added when input rises and released when it falls, all on commodity hardware.
Message passing sits underneath the other traits. Without a boundary between components, there is nothing to contain a failure in and nothing to scale separately.
How Does Message-Driven Communication Work?

Components send each other messages over asynchronous, non-blocking channels instead of calling and waiting for a reply. The sender hands off a message and moves on, and the receiver picks it up whenever it has capacity. Errors come back as messages too.
Each receiver owns a mailbox, a queue it drains at its own pace, and messages are immutable, so no two components share mutable state. An unbounded mailbox only moves the overload from threads to memory.
Non-blocking message passing versus thread-per-request
Tomcat’s default worker pool, which Spring Boot exposes as server.tomcat.threads.max, holds 200 threads, and each request keeps one of them for as long as it waits on a database or remote call.
One slow dependency that keeps 200 requests waiting stalls everything queued behind them. Thread pool exhaustion is exactly what this style of messaging is built to avoid.
With thread-per-request, concurrency is capped by pool size and every waiting call ties up a thread. An event loop works the other way around. A few threads deliver events to handlers, and nothing is allowed to wait.
The Spring Framework reference describes the same split for WebFlux: a small, fixed pool of event loop workers, usually sized to the CPU core count, because nothing on them is supposed to block.
The weak spot is a single blocking call. A Vert.x event loop serving 10,000 requests per second leaves each request 0.1 ms, according to the Vert.x documentation, and Vert.x logs a warning when a loop stays blocked too long.
Location transparency and elastic scaling
The sender addresses a recipient by name, not by host.
The manifesto calls this location transparency: the same messaging semantics hold whether the recipient runs in the same process or on another node. A system can move or add instances without rewriting its callers.
Stateless services scale out by adding instances behind one address. Stateful components scale by sharding, with each shard owning a slice of the data, and the replicas double as the failover targets that resilience relies on.
Adding nodes instead of upgrading one machine is the horizontal-versus-vertical scaling choice, and reactive systems favor the horizontal side. It only works when no central component forces every message through a single point, which is the contention-point constraint the manifesto names.
How Does Backpressure Keep Components From Overloading Each Other?

A slow consumer can tell a fast producer how much it is willing to accept, which keeps queues bounded instead of letting them grow until memory runs out. The manifesto lists it as a flow control tool: shape and monitor the message queues, and push back when necessary.
If the producer simply sends at its own speed, the buffer grows and the failure shows up later as an out-of-memory error or a latency spike. Backpressure turns that around. The consumer requests a number of items, the producer sends no more than that, and buffer size becomes a decision instead of an accident.
Reactive Streams standardizes this as demand signaling. A Subscriber calls request(n), and the Publisher sends nothing until demand has been signaled (Reactive Streams specification).
Version 1.0.0 shipped on April 30, 2015, and JDK 9 later adopted the same interfaces as java.util.concurrent.Flow, which the project describes as one-to-one equivalent. The project’s site lists 1.0.4, from May 26, 2022, as its latest release.
There is one catch in the specification. A publisher may treat a cumulative demand of Long.MAX_VALUE as effectively unbounded, so a subscriber that requests that much has switched backpressure off.
PayPal’s squbs framework runs on Akka Streams and serves over a billion hits a day on as few as eight VMs, according to Lightbend’s case study, which credits streaming backpressure. It is a vendor-published result, so read it as a best case.
How Do Reactive Systems Handle Failure?

The failure stays inside the component where it happened, and a separate supervisor handles recovery so the caller never has to. The manifesto groups this under four terms: replication, containment, isolation and delegation.
Supervision and isolation
Failure is expected, so something else owns the recovery.
Erlang/OTP builds this as a supervision tree, where a supervisor monitors its child processes and restarts them when they fail. Akka took over the model. In Akka’s classic actors the parent decides whether a failed child resumes, restarts, stops or escalates the failure upward, while typed actors declare the same choices through supervision strategies and stop by default (Akka documentation).
OTP also caps restarts so a broken child cannot loop forever. If more than MaxR restarts happen within MaxT seconds, the supervisor terminates all its children and then itself, and the defaults are 1 restart in 5 seconds (Erlang/OTP supervisor documentation).
Set the limits too loose and a child restarts ten times a second, forever, filling the logs with crash reports until someone steps in. Set them too tight and a second crash in quick succession escalates a failure that one more restart would have cleared. The OTP design principles guide warns about both ends and tells teams to tune the defaults for their own workload.
Circuit breaker and bulkhead
A circuit breaker stops calls to a failing dependency, so callers fail fast instead of waiting on timeouts. A bulkhead caps the concurrent calls into one dependency, which keeps a slow one from taking every thread.
Resilience4j’s documentation lists the breaker defaults: it opens at a 50% failure rate measured over a sliding window of 100 calls, then stays open for 60 seconds before probing again.
The minimum number of calls also defaults to 100, so the breaker computes no failure rate until it has recorded that many. A low-traffic service gets little protection from the defaults, and the documentation’s own example says nine failed calls do not open a breaker whose minimum is 10.
An open breaker needs a fallback. Serving cached or reduced data is graceful degradation, and rejecting excess work outright is load shedding.
Netflix put its own Hystrix library into maintenance mode in 2018, froze it at version 1.5.18 and recommended Resilience4j for new projects (Hystrix README). Thoughtworks’ Technology Radar called Resilience4j a default choice in the Java ecosystem in April 2019.
Reactive Architecture vs Reactive Programming vs Event-Driven Architecture
Reactive architecture covers the whole distributed system, while reactive programming structures the logic inside a single component. Event-driven architecture is a communication style, and reactive systems use it alongside messages.
| Approach | Scope | Communicates through | Main goal |
|---|---|---|---|
| Reactive architecture | Whole distributed system | Addressed, asynchronous messages | Resilience and elasticity |
| Reactive programming | One component or service | Event streams and dataflow chains | Efficient, non-blocking logic |
| Event-driven architecture | Event sources and listeners | Events broadcast as facts | Loose coupling of sources and listeners |
Scope separates them more than anything else. The white paper by Jonas Bonér and Viktor Klang calls reactive programming a subset of reactive systems at the implementation level.
Reactive programming lets new information drive the logic, mostly inside one node. Reactive architecture starts where several nodes have to agree on failure, consistency and routing.
Events and messages are different things. A message goes to one specific destination, while an event is a fact that anyone listening can observe (Reactive Manifesto glossary).
A reactive system uses both. Messages carry the communication between components and events record what happened, so a reactive system can sit on top of an event-driven architecture without conflict.
Sources disagree on one point. TechTarget’s definition page presents reactive systems architecture as built on reactive programming, and it names React and Elm, plus RxJava, among its vendors.
The Lightbend white paper reads it differently. It files Elm and Bacon.js along with the Rx libraries under reactive programming, a technique that does not make a system reactive by itself.
The disagreement is about scope, not facts. The manifesto lists system properties and names no library, which supports the narrower reading.
The white paper’s main objection to relying on programming alone is addressability. Pipeline stages are anonymous callbacks, so a failure attaches to a client request instead of to a component that a supervisor can restart.
The recommended pairing is reactive programming inside each component and reactive architecture between them.
How to Design a Reactive System, Step by Step

Start with the failure points instead of the framework. Find what blocks, put asynchronous boundaries there, then add limits, recovery and scaling in that order. The Lightbend white paper makes the ordering argument itself, since resilience has to be designed in and cannot be added retroactively.
- Find what blocks. List every database, remote service and file access, and note what happens when each one stalls. BlockHound, from the Reactor team, reports blocking calls made on non-blocking threads.
- Draw asynchronous boundaries between components, with a mailbox or queue in each gap and immutable message contracts that carry explicit versions.
- Bound every queue, and decide what a full one does to its sender: wait, drop or fail fast.
- Make consumers idempotent. Messages can arrive duplicated or reordered, so processing one twice has to be harmless.
- Give every remote call a timeout and every component a supervisor. Dependencies that fail often get a breaker.
- Make components addressable and replicable. Stateless services run as identical replicas, and stateful ones are sharded by key.
- Test with failures and load. Kill instances, slow a dependency, flood a queue, and watch response time and queue depth while you do it.
Stateless replicas can sit behind a load balancer. Stateful components need routing by key, so every message for one entity reaches the same shard.
Retrofitting is where this usually goes wrong. The white paper warns that sprinkling asynchronous APIs and circuit breakers on a legacy blocking stack does not produce a reactive system.
Quarkus’ own documentation adds the human cost, since moving to non-blocking code means relearning how to structure it.
Frameworks and Runtimes for Reactive Architecture

Akka, Apache Pekko, Eclipse Vert.x, Quarkus and Erlang/OTP supply runtime pieces of a reactive system. Project Reactor with Spring WebFlux, and RxJava, work at a smaller scale, structuring the logic inside a single service.
| Tool | Language or runtime | Role | Level |
|---|---|---|---|
| Akka | JVM (Scala, Java) | Actors, clustering, streams | System and component |
| Apache Pekko | JVM (Scala, Java) | Apache 2.0 fork of Akka 2.6 | System and component |
| Eclipse Vert.x | JVM, several languages | Event loops, event bus | System and component |
| Quarkus | JVM (Java, Kotlin) | Framework on Vert.x and Netty | Service |
| Project Reactor with Spring WebFlux | JVM (Java, Kotlin) | Reactive Streams library and web stack | Component |
| RxJava | JVM | Reactive Extensions library | Component |
| Erlang/OTP | Erlang VM | Processes, supervision trees | System |
A few other names come up constantly. Netty sits underneath Vert.x and Quarkus as the non-blocking network layer. Node.js runs a single event loop per process, where Vert.x runs several. Apache Kafka deserves a separate mention, since it is a durable message log between services and not a framework.
Akka and Apache Pekko after the license change
Akka is no longer Apache licensed.
Lightbend moved it to the Business Source License 1.1 starting with Akka 2.7, released October 26, 2022 (Play Framework documentation). The 2022 announcement made production use free for companies under $25 million in annual revenue (they still need a $0 license from Lightbend), and each version reverts to Apache 2.0 three years after it ships.
The license file for Akka 2.9.3 sets that release’s change date at May 7, 2027.
Apache Pekko forked Akka 2.6 before the change and shipped version 1.0 in July 2023. Play Framework 3.0, released in October 2023, swapped Akka for Pekko, and the Play docs say the move needs only changed akka.* imports and configuration keys.
The two sides tell different stories on security. Lightbend’s Pekko support page lists CVE fixes for its licensed Akka releases, shows none for Pekko 1.0, and says forks made before Akka 23.05 are not SOC 2 compliant. The Play team says Pekko has fixed bugs and security vulnerabilities that Akka 2.6 never received.
The two claims compare against different Akka lines. Play measures Pekko against Akka 2.6, which reached end of life in September 2023, and Lightbend measures it against current licensed Akka.
Mixed models in Quarkus and Vert.x
Quarkus runs on a reactive engine built on Eclipse Vert.x and Netty, yet it lets blocking code share the same application. Its documentation describes a worker-thread switch for methods marked blocking.
Reactive endpoints return Mutiny types and stay on the I/O thread, while imperative endpoints carry the @Blocking annotation and run on workers.
The Vert.x 5 core manual now lists virtual threads next to its event loop model.
When Reactive Architecture Fits and When It Does Not

It fits services that spend most of their time waiting on I/O at high concurrency. It stops paying off when load is steady, data must be strongly consistent, or the team cannot yet debug asynchronous flows.
The good cases tend to look alike: many slow remote calls per request, streaming data and long-lived connections, spiky load with strict latency targets, or components that have to keep running while others fail.
Strongly consistent transactional workloads are a poor match, and so is steady, predictable traffic. So is a team with no experience debugging asynchronous code, which deserves more weight than it usually gets.
A monolithic architecture serving steady traffic from one consistent database gains little from any of this, and pays the full price in complexity.
Costs that arrive with the style
Eventual consistency comes first. Messaging puts partial failures and stale reads in the open instead of hiding them behind a call (Lightbend white paper).
Debugging is the second cost, since a stack trace from an operator pipeline points at the failure and not at where the pipeline was built. The usual fix for that has a price of its own.
In Project Reactor, Hooks.onOperatorDebug() captures a stack trace every time an operator is assembled, and JetBrains’ IDEA documentation says not to use it in production. ReactorDebugAgent adds the same assembly information without the runtime cost, according to the Spring blog (2019).
The Lightbend white paper argues that addressable components make this easier, because the failing component and the message that caused it are identifiable. That is a vendor view, though it follows from the design.
Virtual threads versus reactive programming
The pool-size ceiling that hurts thread-per-request servers applies to platform threads. JEP 444 made virtual threads final in Java 21, and Spring Boot 3.2 (November 2023) runs Tomcat and Jetty request handling on them when spring.threads.virtual.enabled is true.
A 2026 study from Lublin University of Technology (Journal of Computer Sciences Institute 39) ran identical Spring Boot 3.5.3 CRUD services on Google Cloud, with JDBC on virtual threads against R2DBC on WebFlux.
At 1,000 concurrent requests on 32 vCPUs, virtual threads averaged 1,540 ms against 9,067 ms for WebFlux. They also used 5,887 MiB of heap against 1,596 MiB.
Virtual threads won on response time and throughput in every tested scenario, and the hypothesis that they saturate the CPU sooner was not supported. Reactive used less memory in every scenario.
Earlier work points the other way. Iwanowski and Kozieł (2022) compared WebFlux on Netty with Spring MVC on Tomcat on JDK 11. At 2,000 simultaneous requests the reactive application returned a single record 31% to 33% faster, and it was almost three times faster on an endpoint with a 10-second delay, where the Tomcat application rejected half the requests in the 2-CPU setup. Across all scenarios the reactive application averaged 28.9% CPU against 43.2% for the imperative one, and RAM use did not differ significantly.
The imperative stack came out ahead on one operation, fetching all records on the 4-CPU setup, by 7% at 2,000 requests and 2% at 300.
The 2022 comparison pits reactive against platform-thread pools, and the 2026 one against virtual threads. The workloads also differ, and the 2026 authors say results may change for CPU-bound or highly distributed systems.
Virtual threads answer a code-level question, which is how to write concurrent code inside one service. They add no backpressure between services, no supervision and no elastic scaling, so those traits still have to be designed.
For a blocking-style service with enough memory, virtual threads fit best. Memory-tight services, streaming and many open connections still favor reactive programming. Calls between services need the architecture-level traits whichever one you pick.
Reactive Architecture FAQ
How does reactive architecture relate to microservices?
The two are independent choices that fit well together. The Lightbend white paper assigns reactive programming to the logic inside one microservice, while reactive architecture governs how the services communicate and what happens when they fail or scale.
Is the actor model the same thing as reactive architecture?
No. The actor model is one way to build message-driven components, used by Akka and its fork Apache Pekko, and by Erlang/OTP.
The manifesto does not prescribe it, and Quarkus and Vert.x reach non-blocking message passing through event loops instead.
Do you need Kafka or another message broker to build a reactive system?
No. Components inside one process can exchange messages through in-memory mailboxes, which meets the message-driven requirement.
A broker such as Apache Kafka adds a durable, replayable log between services, and its consumers still have to tolerate redelivery.
Does using React or Elm make an application reactive in this sense?
No. React and Elm update an interface when state changes, which is reactive programming at most.
A reactive system also needs message boundaries and supervision, plus elastic scaling, and a front-end library supplies none of those.
Choosing the First Service to Move to Reactive Architecture
Pick the service that spends the most time waiting on a slow dependency and already shows thread pool exhaustion or queue growth under load. That one pays back fastest.
Baseline response time first, along with pool saturation and queue depth. Next comes a timeout around the dependency, with a breaker and a bulkhead. Only then put a bounded queue and an asynchronous boundary behind it.
Containment goes first because it changes no calling code, and the baseline proves whether the change worked.
The queue costs eventual consistency at that boundary, since callers read data that can lag behind the latest message.
As of October 2026 the order holds for stacks that still cap request threads. On Java 21 with virtual threads enabled, the baseline should first confirm that the pool is still the bottleneck.
- What Communications Teams Should Ask Before Commissioning a Mobile App - November 12, 2026
- How to Install Node JS in VS Code and Run Your First Script - November 11, 2026
- Protecting Users and Brand Standards Across Image-Driven Platforms - November 10, 2026



