Reactive Streams and Spring WebFlux¶
Spring WebFlux is the reactive web framework of Spring Framework. It embraces asynchronous, non-blocking I/O and the Reactive Streams specification to build scalable, resource-efficient applications for modern cloud and microservice environments.
This page introduces the Reactive Streams specification and its implementations (Project Reactor, RxJava 3, SmallRye Mutiny, and the Java Flow API), and shows how Spring WebFlux uses them. Everything is demonstrated with short code fragments that you can run with a plain Java main method — no server required.
Audience and scope
This page is for developers new to reactive programming who want to understand the building blocks before writing a WebFlux application. For the application itself, continue to Create a WebFlux application from scratch or Create a WebFlux application with Spring Boot.
Reactive Streams: the specification¶
Reactive Streams — official site reactive-streams.org — is an initiative to provide a standard for asynchronous stream processing with non-blocking backpressure. It encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.
The JVM specification is a small set of four interfaces, defined in the org.reactivestreams package:
Publisher<T>— produces a potentially unbounded number of sequenced elements, publishing them according to the demand received from itsSubscriber(s).Subscriber<T>— receives the signals produced by aPublisher:onSubscribe, then zero or moreonNext, and finallyonCompleteoronError.Subscription— represents the one-to-one lifecycle between aSubscriberand aPublisher. It carries the demand signal (request(long n)) andcancel().Processor<T, R>— a processing stage that is both aSubscriberand aPublisher, obeying the contracts of both.
The interfaces are intentionally tiny:
public interface Publisher<T> {
void subscribe(Subscriber<? super T> s);
}
public interface Subscriber<T> {
void onSubscribe(Subscription s);
void onNext(T t);
void onError(Throwable t);
void onComplete();
}
public interface Subscription {
void request(long n);
void cancel();
}
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Nothing is delivered until the Subscriber signals demand. This is the essence of backpressure: the consumer controls the flow of data, so a fast producer cannot overwhelm a slow consumer.
A minimal Publisher and Subscriber¶
The following fragment wires a simple Publisher to a Subscriber by hand. It demonstrates the full signal protocol — no reactive library required, only the org.reactivestreams interfaces:
// A Publisher that emits 1, 2, 3 and completes.
Publisher<Integer> publisher = subscriber ->
subscriber.onSubscribe(new Subscription() {
private int value = 1;
@Override
public void request(long n) {
for (long i = 0; i < n && value <= 3; i++) {
subscriber.onNext(value++);
}
if (value > 3) {
subscriber.onComplete();
}
}
@Override
public void cancel() {
}
});
publisher.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
s.request(3); // signal demand explicitly
}
@Override
public void onNext(Integer item) {
System.out.println("received: " + item);
}
@Override
public void onError(Throwable t) {
System.err.println("error: " + t);
}
@Override
public void onComplete() {
System.out.println("done");
}
});
Running it prints:
In practice you never assemble these interfaces by hand — a reactive library such as Project Reactor does it for you — but the protocol explains what happens behind every Flux and Mono you will use in WebFlux.
Implementations are validated by a conformance test kit (the Reactive Streams TCK), which guarantees the interoperability of any compliant library.
Project Reactor¶
The Reactor project works closely with the Spring team, and Reactor is the reactive library that Spring WebFlux uses by default. Useful entry points: the reference documentation and the API Javadoc for Flux and Mono.
Reactor provides two types that implement org.reactivestreams.Publisher:
Flux<T>— aPublisherthat emits 0 to N elements, and then completes either successfully or with an error.Mono<T>— aPublisherthat emits at most one item viaonNextand then terminates with anonCompletesignal (a successfulMono, with or without a value), or emits only a singleonErrorsignal (a failedMono).
| Concept | Reactor | Analogy |
|---|---|---|
| 0..1 result, maybe asynchronous | Mono<T> |
CompletableFuture<T> / Optional<T> |
| 0..N results, streamed | Flux<T> |
Stream<T> / Iterable<T> |
Creating a Flux and a Mono¶
Add Reactor to your project (the coordinates are managed by spring-boot-starter-parent):
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
The most common factory methods are static:
Flux<String> flux = Flux.just("a", "b", "c"); // fixed data
Flux<Long> interval = Flux.interval(Duration.ofSeconds(1)); // ticks every second
Flux<Integer> generated = Flux.range(1, 5); // 1..5
Mono<String> mono = Mono.just("hello"); // single value
Mono<String> empty = Mono.empty(); // completes without a value
Mono<String> fromCallable = Mono.fromCallable(() -> expensiveBlockingCall()); // lazily wrap blocking code
Subscribing: nothing happens until you subscribe¶
Reactive types are lazy by default. Assembling operators builds a pipeline; subscribing executes it:
Flux.just("a", "b", "c")
.map(String::toUpperCase)
.subscribe(
data -> System.out.println("onNext: " + data),
err -> System.err.println("onError: " + err),
() -> System.out.println("onComplete")
);
Output:
Common pitfall
A Flux or Mono that is never subscribed emits nothing. Inside a WebFlux @RestController you return the reactive type and Spring subscribes for you; in plain Java code you must call subscribe() (or a blocking method such as block()) yourself.
Transforming and combining streams¶
The operator style reads like the Java Stream API, but the operations are asynchronous:
Flux.just(1, 2, 3)
.map(i -> i * 10) // transform each element
.filter(i -> i > 10) // keep elements matching a predicate
.flatMap(i -> fetchRelated(i)) // 1-to-N async transformation
.concatWith(Flux.just(100)) // append another stream
.take(2) // only the first 2 elements
.subscribe(System.out::println);
Combine several streams with zip — this pattern appears throughout the sample repository, for example to pace a data stream with a timer:
Flux.zip(Flux.interval(Duration.ofSeconds(1)), Flux.just("a", "b", "c"))
.map(Tuple2::getT2)
.subscribe(System.out::println);
// prints "a", then "b", then "c" — one per second
Backpressure: demand in action¶
Reactor manages demand for you, and exposes it when you want to control it explicitly:
Flux.range(1, 100)
.subscribe(new BaseSubscriber<Integer>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(1); // ask for one item at a time
}
@Override
protected void hookOnNext(Integer value) {
System.out.println("received: " + value);
request(1);
}
@Override
protected void hookOnComplete() {
System.out.println("done");
}
});
Operators such as limitRate, buffer, and onBackpressureDrop reshape demand when the default behavior does not fit.
Error handling¶
Errors are signals, not exceptions thrown at the caller:
Flux.just("a", "b")
.map(s -> { if (s.equals("b")) throw new IllegalStateException("boom"); return s; })
.onErrorResume(e -> Flux.just("fallback"))
.subscribe(System.out::println);
// prints: a, fallback
For tests, reactor-test provides StepVerifier, which verifies the exact sequence of signals:
See Testing for a full treatment.
Other implementations¶
Spring WebFlux is unopinionated about the reactive library. Any Publisher implementation can be a controller return type, and Spring adapts between supported reactive types automatically.
RxJava 3¶
RxJava 3 implements Reactive Streams with Flowable (backpressured), Observable (unbackpressured), Single, Maybe, and Completable. RxJava 2 is end of life and removed from Spring's supported adapters; prefer RxJava 3 for new work.
// io.reactivex.rxjava3:rxjava
Flowable.just("a", "b", "c")
.map(String::toUpperCase)
.subscribe(System.out::println);
See RxJava 3 for the WebFlux integration.
SmallRye Mutiny¶
SmallRye Mutiny is a Reactive Streams implementation from Red Hat, supported by Spring since Framework 5.3.10. Its two core types are Uni<T> (0..1, similar to Mono but accepts null) and Multi<T> (0..N, similar to Flux).
// io.smallrye.reactive:mutiny
Uni.createFrom().item("hello")
.onItem().transform(String::toUpperCase)
.subscribe().with(System.out::println);
See SmallRye Mutiny for the WebFlux integration.
The JDK Flow API¶
The JDK ships java.util.concurrent.Flow, which packages the Reactive Streams interfaces inside the JDK and adds a ready-made SubmissionPublisher implementation. The JDK also includes a reactive java.net.http.HttpClient built on the Flow API.
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
publisher.subscribe(new Flow.Subscriber<>() {
@Override
public void onSubscribe(Flow.Subscription subscription) {
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(String item) {
System.out.println("received: " + item);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
System.out.println("done");
}
});
publisher.submit("hello");
publisher.close();
Spring adapts between Flow.Publisher and Reactive Streams types — see The JDK Flow API and the runnable java9 sample.
Choosing an implementation¶
| Library | Core types | Backpressure | Notes |
|---|---|---|---|
| Project Reactor | Mono, Flux |
Yes | Default in Spring; richest operator set; reactor-test for verification |
| RxJava 3 | Flowable, Observable, Single, Maybe, Completable |
Flowable only |
Adapted types supported in WebFlux controllers |
| SmallRye Mutiny | Uni, Multi |
Yes | Simple, event-first API; Uni accepts null |
| JDK Flow | Flow.Publisher |
Yes | JDK-native; minimal operators; useful for interop |
For new Spring projects, start with Project Reactor: it is what Spring Data, Spring Security, Spring Session, and the WebFlux server internals are built on.
How Spring WebFlux uses the reactive stack¶
Spring WebFlux builds on Reactive Streams and Project Reactor by default, but stays unopinionated about the reactive runtime. The key integration points:
- Reactive return types. Controller methods and handler functions may return
Mono<T>,Flux<T>,CompletableFuture<T>, or anyorg.reactivestreams.Publisher<T>. Spring adapts between supported reactive types automatically. - Two programming models. Annotated controllers (
@RestControllerwith@GetMapping,@PostMapping, and friends) or functional handlers (RouterFunction+HandlerFunction) — see the Reactive Web chapter. - A reactive HTTP client.
WebClientperforms non-blocking HTTP requests and integrates with Reactor and other reactive types — see WebClient. - Server runtimes. Reactor Netty is the default server for Spring Boot's WebFlux starter; Tomcat, Jetty, and Undertow are also supported — see the bootstrapping options in Create a WebFlux application from scratch.
- The wider ecosystem. Spring Data (R2DBC, reactive repositories), Spring Security (
ServerHttpSecurity, reactive user details), and Spring Session (reactiveWebSessionstorage) all accept and return Reactive Streams types — the following chapters cover each of them.
References¶
Specification
- Reactive Streams specification — the JVM specification, TCK, and examples
- reactive-streams.org — the official site
org.reactivestreamsJavadoc —Publisher,Subscriber,Subscription,Processor
Projects
- Project Reactor — reference docs, API Javadoc,
Flux,Mono - RxJava 3 — 3.x Javadoc
- SmallRye Mutiny — Javadoc
- Reactor Netty — the default WebFlux server runtime
- Spring WebFlux reference — the official framework documentation
JDK
java.util.concurrent.FlowandSubmissionPublisherjava.net.http.HttpClient— the JDK's reactive HTTP client
Where to go next¶
- Create a WebFlux application from scratch — the low-level building blocks (
HttpHandler,WebHttpHandlerBuilder, Reactor Netty). - Create a WebFlux application with Spring Boot — the fast path with starters and auto-configuration.
- An introduction to Reactive Web — the
WebHandlerandHttpHandlerarchitecture of WebFlux.