News and Events

Reactor Bismuth release train first milestone available

On behalf of the Reactor team, it is my pleasure to announce that Reactor hit an important milestone last week, making the Bismuth-M1 release train available.

This first milestone backs the newly released Spring Framework 5 RC1. It notably includes version 3.1.0.M1 of reactor-core, reactor-test and reactor-extra.

As the 3.1.x generation is slated to be the long term support branch (as is appropriate for a version that backs the Spring framework), focus has been on stabilizing and polishing the API. As such, expect some breaking changes from the 3.0.x versions [1].

Migrating from 3.0.x

If you’ve kept your Reactor dependencies up to date during the 3.0.x phase (meaning you’re on reactor-core 3.0.7), then you’ve noticed methods being deprecated in the last few versions.

We’ve tried to prepare a path to 3.1.0 as much as possible by providing new APIs in advance and, in the case of renamed methods, deprecating the old methods when the new one were introduced.

These methods that were deprecated in 3.0.x have mostly been removed in 3.1.0.M1, so make sure to follow this quick guide on migrating before you update your dependencies!

In a very limited number of cases, this strategy couldn’t be applied and we’ll detail the migration pattern for these below.

Hunt for deprecated methods

Deprecation notes are present in 3.0.7 for most of API changes, so the easiest way to start migrating is to find deprecated API usage in your code and fix them according to the suggestion in the javadoc.

Most of the API changes fall into 2 categories:

  1. renaming a method to better align the APIs between Flux and Mono or to remove some ambiguities with lambda usage: the new alias will be introduced in 3.0.7 and old method will be deprecated with a note to use the new alias.

  2. removing redundant variants of operators like all the xxxMillis variants (that have been removed in favor of just using the Duration based alternatives): the method will be deprecated with a note to use the variant that will be kept.

One trickier case exists though:

Migrating Mono.then(Function) and Mono.flatMap

Contrary to all other then variants, which have the effect of ignoring the data from the mono then continuing it upon completion with another Publisher, Mono.then(Function) would react on the onNext event.

This was violating the principle of least surprise and could lead to unexpected behavior when chaining a few then together. Take this example:

 	 .then(v -> AsyncUtils.toJson(v));

This code would never invoke runCleanupFor, because the thenEmpty just before returns a Mono<Void>. But if the code used in the Function is happy compiling with a Void generic type, then you won’t get any warning.

Looking further at the signature and the fact that the operator didn’t entirely ignore onNext signals like its other variants, we noticed that this was actually closer to a classic flatMap:

Mono<V> then(Function<T, Mono<V>> thenFunction);

But flatMap already existed in Mono, with the following signature:

Flux<V> flatMap(Function<T, Flux<V>> mapFunction)

Since flatMap usually returns the same type as the enclosing type, it seems more correct to rename then(Function) to flatMap and the old flatMap would become flatMapMany (following an already established suffix convention that indicates the Mono is transformed to a Flux by such operators).

Since a new flatMap signature only differing by its return type couldn’t be introduced in 3.0.x (where we only wanted to deprecate the old flatMap), this migration was trickier to anticipate.

As a result, the best migration "recipe" is:

  1. to first replace all usage of Mono.flatMap with flatMapMany

  2. finish other refactor and migrations

  3. switch to 3.1.0.M1

  4. replace all usage of Mono.then(Function) (now not compiling) to flatMap

Note that if you don’t perform step 1, you might get misleading compilation errors: sequences that contained a flatMap would continue as a Flux but now continue as a Mono.
If down the chain of operators you relied on being in a Flux (eg. using an operator only available on this type, like reduce()), the compilation error would appear down the line.
Rather than fixing the compilation error at face value, you’d need to notice that the code expected a Flux and that this is due to not using flatMapMany upstream…​

Schedulers, time and testing with virtual time

The Schedulers.timer() scheduler has been removed, and all default schedulers are now capable of submitting a task with a delay/periodically. As a result, for operators like delay() the default Scheduler is now Schedulers.parallel(). Note that thread names will now follow the "parallel-`x`" pattern, with x varying between the number of workers instead of a single "timed-`n`".

Another change is the way the VirtualTimeScheduler from reactor-test now can replace any Scheduler. Most notably, when used through StepVerifier#withVirtualTime, the VTS will indeed replace ALL default Schedulers.

This means that if you are testing blocking code that you would previously isolate in eg. Schedulers.elastic(), you now need to do that in a dedicated Scheduler created before the StepVerifier (eg. Schedulers.newSingle()).

Processor changes

Processors have been reworked. Notably you don’t need to explicitly connect to some Processors anymore, but should rather always use the new sink() method. This aligns the API for all processors and serializes calls for those which aren’t already inherently serialized. The whole Processor family usage is now closer to Flux.create.

New Features


The Scannable interface replaces introspection interfaces that were mainly used internally and have been removed (Loopback, MultiProducer, MultiReceiver, Producer, Receiver, Trackable).

The goal is to support introspection of sequences, including by traversing the operators, in a single interface. The focus will increase on supportability using this mechanism between now and the GA release.


The delayUntil and delayUntilOther operators were added to Mono in order to delay the emission of the Mono until after a companion Publisher completes. In the case of delayUntil, that companion is generated from the value emitted by the source Mono. The triggering of the delaying Publisher is made upon source’s completion though. This is very close to the very recently added untilOther, except that the later triggers its companion on onNext rather than onComplete.

Decision is still pending whether or not untilOther should be deprecated, as we believe both its name and its onNext-dependent behavior are less expressive and useful.


Let me conclude by a shoutout to community contributors that participated in the latest 3.0.x releases as well as the M1 release (github usernames in alphabetical order): @bdavisx @Dmitriusan @garyrussell @lebannen @lhotari @madhead @nebhale @rajinisivaram @RunninglVlan @sdeleuze @schauder.

Thanks again guys!

To get this release, use the BOM together with the Spring Milestone repository (see the reference guide here).

Finally, a call to action:

If you have any question about this milestone, or even more importantly any remarks or suggestions, don’t hesitate to join the discussion live on Gitter or open an issue.

Happy reactive programming!

1. As a reminder, Reactor 3 is not entirely conforming to the MAJOR.MINOR.PATCH scheme of semantic versioning, but rather follows a X.MAJOR.MINOR versioning scheme. X incrementing means a major architectural and API do-over, a MAJOR can contain important new features and breaking changes while MINOR are all binary and API compatible within the same MAJOR (although they can contain internals changes and new features in addition to bugfixes)
comments powered by Disqus