Open-source project
reactor/reactor-core avatar
reactor/reactor-core

Reactor Core: the reactive streams library most of Spring never names

Non-Blocking Reactive Foundation for the JVM

5,241 stars1,265 forksJavaApache-2.0

At a glance

What is it?
Java 8 and up, Apache-2.0, and the operator vocabulary behind WebFlux: what Flux and Mono actually guarantee, why the build needs three JDKs, and what a release train means for your dependency version.
Who is it for?
Reactor Core earns its scale because it is the layer everything else in the JVM reactive ecosystem sits on, and because its two types are a genuinely small idea: an unbounded stream and a single result. The parts that surprise newcomers are not the operators but the tooling and the release process.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • reactor/reactor-core5,241 ★ · +1
Star history
Repository2026-10-072026-10-22
reactor/reactor-core5.2k5.2k

1 Stars in the last 15 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Two publisher types and the gap between them

The whole public surface of Reactor is two classes. Flux is a Reactive Streams Publisher with basic flow operators, described as having static factories for building sources from arbitrary callback types, plus instance methods that materialise on each subscription or that multicast through operations such as publish and publishNext. Mono is a Publisher constrained to zero or one element, with static factories for deterministic zero-or-one sequences and instance methods that materialise on subscribe or on get.

The distinction between them is where most design decisions get made. A Flux is for anything that could arrive again: a timer tick, a queue drain, a stream of events from a socket. A Mono is for anything with exactly one answer: a lookup, a future, a single value read from a cache. The README's own examples make the split obvious, because one uses Flux and the other uses Mono.

java
Flux.fromIterable(getSomeLongList())
    .mergeWith(Flux.interval(100))
    .doOnNext(serviceA::someObserver)
    .map(d -> d * 2)
    .take(3)
    .onErrorResume(errorHandler::fallback)
    .doAfterTerminate(serviceM::incrementTerminate)
    .subscribe(System.out::println);
java
Mono.fromCallable(System::currentTimeMillis)
    .flatMap(time -> Mono.first(serviceA.findRecent(time), serviceB.findRecent(time)))
    .timeout(Duration.ofSeconds(3), errorHandler::fallback)
    .doOnSuccess(r -> serviceM.incrementSuccess())
    .subscribe(System.out::println);

The Flux example merges two sources, transforms and caps the result at three elements. The Mono example takes a timestamp, races two lookups against each other, and falls back on a three second timeout. Both are single expressions rather than nested callbacks, which is the actual argument for a reactive library: it changes the shape of the code, not just the performance profile.

Eight repositories and what each module is for

The tree shows that reactor-core is a multi-project Gradle build rather than a single artifact. `reactor-core/` is the library itself. `reactor-test/` is its testing counterpart, and the Gradle snippet in the README pairs them at the same version, which is the convention to follow. `reactor-tools/` is the Java agent, and the README notes it has lived in this repository since the 3.3.x line.

`reactor-core-micrometer/` is the observability bridge. The 3.8.6 release notes confirm it is real rather than aspirational: that release depends on Micrometer 1.16.6 and Micrometer Tracing 1.6.6. `benchmarks/` holds performance measurement code, `buildSrc/` holds shared build logic, `codequality/` holds static analysis configuration, and `docs/` holds the Antora site.

There is also a `.sdkmanrc` at the root, which is a small tell about who is expected to build this. It tells SDKMAN what the project's JDK baseline is, so the tool the README recommends for installation is preconfigured for the repository.

GitHub reports 5,236 stars, 1,260 forks and 125 open issues under the Apache-2.0 license, with projectreactor.io as the homepage and a last push on 2026-09-21. The fork count relative to stars is high, which is what you would expect from a library that arrives transitively through Spring rather than being chosen directly.

Java 8 to run, three JDKs to build

This is the contradiction most people hit first. The README states plainly that Reactor 3 requires Java 8 or later to run. Twenty lines later, the build section explains that building the project needs JDK 8, 9 and 21 available at once, because the project produces a multi-release JAR.

shell
sdk env install

That requirement is not arbitrary. A multi-release JAR carries different class files for different Java versions in one archive, so the compiler has to actually run under the older versions to produce the older class files. Gradle's toolchain support is expected to auto-detect or auto-provision them, and the README only asks you to install them manually when auto-detection fails.

The failure message to watch for is specific enough to be worth memorising: no matching toolchains found for requested specification, where the specification is 8, 9 or 21. The recommended fix is SDKMAN, with one grep-based command per missing JDK.

shell
sdk install java $(sdk list java | grep -Eo -m1 '9\b\.[ea|0-9]{1,2}\.[0-9]{1,2}-open')

The documentation build adds a fourth constraint on top of that: the active shell JDK must be 17 or higher for Antora to run, so JDK 21 should be the current one when you build docs. PDF output into the generated zip needs the asciidoctor-pdf command on your PATH, which on a Mac is a brew install away.

Release trains, and a line that has already left support

The versioning scheme here is not semver alone, and the three most recent releases are enough to show why that matters. Version 3.8.7 belongs to two trains at once, described in its own release notes as part of the 2025.0.7 and 2026.0.0-M1 release trains. Version 3.8.6 belongs to the 2025.0.6 train. Version 3.7.19 belongs to the 2024.0.18 train and is explicitly the last release of the 3.7.x line, because that train is going out of open source support.

So a single artifact version maps onto a commercial support window, and the mapping is not derivable from the version string. In practice this means choosing between the 3.8 line and the 3.7 line is a support question rather than a feature question, and the support policy lives on projectreactor.io rather than in the repository.

The content of those three releases is a useful sample of what maintenance on a library at this scale looks like. The 3.8.7 notes list three bug fixes, all in time-sensitive internals: elapsed time computation in the optimistic emit failure handler, a fair-backpressure index wraparound in window timeout, and a flush-state guard in a buffer-with-timeout operator. The 3.8.6 notes are mostly dependency bumps for ByteBuddy and BlockHound plus documentation work, including moving Javadoc links from javadoc.io to jspecify.dev.

Blocking calls are a runtime failure, not a style warning

The most interesting dependency in this library is one you never import. BlockHound appears in the 3.8.6 release notes as a dependency that gets bumped, and its job is detecting blocking calls on non-blocking threads. That turns the classic reactive mistake, calling a blocking API inside a pipeline, from a silent throughput problem into a thrown error.

The companion piece is `reactor-tools`, the Java agent mentioned in the README as a debugging aid for Reactor code since 3.3.x. In practice the pairing means Reactor can point at the exact call site that blocked, which is the difference between a stack trace full of framework frames and one that names your code.

Because this detection depends on bytecode instrumentation, it also explains a constraint that the README does not spell out. Runtime bytecode enhancement is not available on every JVM in the same way, and Android is the case the README addresses directly: Reactor 3 does not officially support or target Android, though it should work fine with SDK 21 and above, with the fuller note in the reference guide.

That wording is a good example of a carefully scoped promise. Untargeted means the build does not produce Android artifacts or test against them. Probably fine means the maintainers believe it works on the platform versions they name. For an application that must ship to Android, that is a risk you accept and test yourself rather than a guarantee you rely on.

What the README hands you and what it sends you to read

The README is a good map and a poor manual. What it gives you is a dependency snippet with the exact current version, a note about snapshots being behind a commented repository line, the build toolchain instructions, and two short operator examples. What it sends you elsewhere is everything that actually matters once you are writing code: the reference documentation for getting started with Maven, for prerequisites and the full Android note, and for the operator-by-operator API.

It also points at two learning paths with different assumptions. Newcomers to reactive programming are pointed at a hands-on introduction repository, while people coming from RxJava or wanting more detail are pointed at an InfoQ article built around examples. That split is a fair admission that the library's onboarding burden is the real barrier, not its documentation.

The project description is four words, Non-Blocking Reactive Foundation for the JVM, and the README expands it into a Reactive Streams implementation with a Reactive Extensions inspired API plus efficient event streaming support. Reactive Streams is the specification; Reactive Extensions is the vocabulary; Reactor is the implementation of both for Java. Knowing which layer a question belongs to is most of what you need before you start reading the API pages.

Editorial conclusion

Reactor Core earns its scale because it is the layer everything else in the JVM reactive ecosystem sits on, and because its two types are a genuinely small idea: an unbounded stream and a single result. The parts that surprise newcomers are not the operators but the tooling and the release process. Knowing that the library has to detect blocking calls at runtime, that it ships a Java agent for that purpose, that the build compiles a multi-release JAR against three JDK versions, and that 3.7.x has already left open source support while 3.8.x is current, tells you more about operating this dependency than any operator list will. Read the reference documentation on projectreactor.io, decide your line against the release train policy, and treat the Android note as exactly what it says: untargeted but probably fine.

Frequently asked questions

Is a reactor core still active?

The one in this repository is a Java library rather than a nuclear component, and it is actively worked on, with a push recorded on 2026-09-21 and three releases in 2026 alone. The 3.8 line is the current one, and the 3.7 line has already had its final release as that support window closes. If you encountered the phrase in news coverage of a nuclear plant, that is a different meaning entirely and nothing to do with this code.

What is the difference between Flux and Mono?

Flux publishes a sequence of zero or more elements, so it fits timers, queue drains and event streams. Mono is constrained to at most one element, so it fits a lookup, a computed value or a completed action. Both are Reactive Streams publishers and both are lazy: nothing runs until you subscribe. Choosing between them is mostly a statement about whether the operation can produce more than one result.

Why does building Reactor need JDK 8, 9 and 21 at the same time?

Because the project produces a multi-release JAR, which packs different class files for different Java versions into one archive. The compiler has to run under each target version to generate those class files, so the build needs toolchains for 8, 9 and 21. Running the library only needs Java 8 or later, so this constraint applies to contributors building from source, not to applications depending on the artifact.

Which Reactor version should a new project use?

The 3.8 line, since 3.7.x has reached its final release and is leaving open source support. Be aware that artifact versions map onto dated release trains rather than pure semantic versioning, and one version can belong to two trains at once, so check the support policy on the project site before pinning. If a framework you depend on constrains you to a 3.7 range, that transitively pins you to a line whose support window is closing.

Does Reactor work on Android?

The README says Reactor 3 does not officially support or target Android, while adding that it should work fine with SDK 21 and above. Nothing is built or tested against Android, so you are relying on the maintainers' assessment rather than a compatibility guarantee. The reference guide has a longer prerequisites note that is worth reading before committing to it.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. reactor/reactor-core on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/reactor-reactor-core.svg)](https://hysenlabs.com/projects/reactor-reactor-core)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/reactor-reactor-core.svg)](https://hysenlabs.com/projects/reactor-reactor-core)