VMware offers training and certification to turbo-charge your progress.Learn more
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
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 .
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.
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:
renaming a method to better align the APIs between
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.
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:
Contrary to all other
then variants, which have the effect of ignoring the data from the mono then continuing it upon completion with another
Mono.then(Function) would react on the
This was violating the principle of least surprise and could lead to unexpected behavior when chaining a few
then together. Take this example:
Mono.just(someObject) .thenEmpty(Mono.fromRunnable(AsyncUtils::runDiagnostics)) .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
Mono<V> then(Function<T, Mono<V>> thenFunction);
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
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:
to first replace all usage of
finish other refactor and migrations
replace all usage of
Mono.then(Function) (now not compiling) to
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
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
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
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
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
Scannable interface replaces introspection interfaces that were mainly used internally and have been removed (
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.
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
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)