Reactive Programming: The Hitchhiker’s Guide to map operators

Benedikt Jerat
Oct 8 · 6 min read
Photo by Muhammad Daudy on Unsplash

When starting with Reactive Programming, some operators are easy to grasp and some are rather difficult to understand. Some operators share a common purpose, like map and flatMap that both transform the input type into some potential different output type. Both are used for different purposes and especially in the beginning it can be difficult to differentiate between all those similarly named operators.

Reminiscing my own learning curve, I will introduce you to the following map operators in this blog post: map, flatMap, concatMap, flatMapSequential, and switchMap. I will briefly explain the (sometimes) subtle differences between those five operators, so that you can apply them properly in your day to day work.

In the examples I will use marble diagrams for illustration. If you don’t feel confident with them yet, I recommend reading one of my earlier blog posts as an introduction to the topic: Reactive Programming in a Nutshell

One final note before starting: The operators are all taken from the Project Reactor framework. Other frameworks might have different names for the same operators. So watch out.

map

This operator is the easiest of the five to understand. No asynchronicity, no inner subscriber, no interleaving.

https://projectreactor.io/docs/core/release/api/reactor/core/publisher/doc-files/marbles/mapForFlux.svg

Values passed to the map function are just transformed from input value to output value synchronously:

Flux
.just("Java", "Kotlin", "Scala", "Groovy")
.map { it.length }
.subscribe { print(it) }
// 4656

The map operator is the only operator in this series that directly returns a value and not a publisher (Mono or Flux) itself. The operator is typically used for 1:1 transformations from one type to another or extracting values out of the input type.

flatMap

This is one of the four operators in this blog post that takes a function that itself returns an asynchronous publisher to subscribe upon.

https://projectreactor.io/docs/core/release/api/reactor/core/publisher/doc-files/marbles/flatMapForFlux.svg

For every outer emission, the inner publisher is eagerly subscribed to. In comparison to the map operator, the inner emissions are flattened into the resulting sequence. However, the initial ordering is not guaranteed to be preserved as emitted items are propagated as they come. Emissions from different inner publishers may interleave, as can already be seen in the marble diagram.

Let me show you a small example that shows flatMap in action:

infrastructureRepository
.resolveLines(deployment) // returning Flux
.flatMap { lineDescription ->
deploymentService.deployLine(deployment, lineDescription)
}

The deployment contains some metadata to be able to resolve corresponding lines for this application. For every emitted line the DeploymentService is called with the deployment information and the line description.

In case you want some kind of blue-green deployment you absolutely should not use flatMap here. As described earlier, the inner publishers are being eagerly subscribed to. Therefore, deployments for hosts on different lines may run at the same time, which contradicts the idea of blue-green deployments.

The operator is useful, when order does not matter and interleaving is no problem, which actually applies to many use cases. For example, when we perform an HTTP request for every emitted item of the outer publisher and these only return a single value, we’re not in danger of interleaved values.

When strict ordering is necessary and values from different inner publishers must not interleave, then have a look at the concatMap operator.

concatMap

The concatMap operator is actually quite similar to the previous one, except that the operator waits for its inner publishers to complete before subscribing to the next one.

https://projectreactor.io/docs/core/release/api/reactor/core/publisher/doc-files/marbles/concatMap.svg

As the name suggests, the emitted values from different inner publishers are concatenated instead of being flattened into the resulting sequence. The usage is identical to the flatMap example, except the different operator:

infrastructureRepository
.resolveLines(deployment) // returning Flux
.concatMap { lineDescription ->
deploymentService.deployLine(deployment, lineDescription)
}

Now, all hosts of one line are being deployed before continuing with the next line, which allows an actual blue-green kind of deployment style.

In comparison to flatMap one must admit that concatMap is potentially less performant. Whereas with flatMap the total runtime mainly depends on the slowest publisher because of its eager subscription to inner publishers, concatMap waits for its inner publisher to finish before continuing with the next item. As a compromise between flatMap and concatMap, the operator flatMapSequential exists.

flatMapSequential

This operator eagerly subscribes to its inner publishers like flatMap, but queues up elements from later inner publishers to match the actual natural ordering and thereby prevents interleaving like concatMap.

https://projectreactor.io/docs/core/release/api/reactor/core/publisher/doc-files/marbles/flatMapSequential.svg

The operator can be used very similar to flatMap or concatMap before:

infrastructureRepository
.resolveLines(deployment) // returning Flux
.flatMapSequential { lineDescription ->
deploymentService.deployLine(deployment, lineDescription)
}

The buffer size for the queue can be configured via one of its overloaded variants, if required.

In general, the operator is suitable for situations, where ordered non-interleaved values of concatMap with the performance advantage of flatMap is required and the queue is not expected to grow infinitely.

switchMap

The switchMap operator must not be confused with concatMap, as it looks very similar at the first glance. However, it works by cancelling the previous inner subscriber whenever the outer publisher emits an item.

https://projectreactor.io/docs/core/release/api/reactor/core/publisher/doc-files/marbles/switchMap.svg

The name of the operator is derived from this switch from the previous to the new publisher. As long as the outer publisher does not emit a new item, values from the current inner publisher will be propagated to the result sequence.

Now the big question is: When could this behaviour be useful?

For our previous example with the deployments, this would actually be horrible. In that case, it cancels the deployment of the current line, when the infrastructure repository emits the information for the next line.

However, in a scenario where the previous emitted values are not relevant anymore, but only the current ones, this operator can be used. For example for an infinite stream of hot data, like key strokes in a user search. If a user types a new character, we immediately want to update the search and we don’t care about previous search results, because they’re actually outdated. In this exact example, the switchMap operator will most likely be combined with debouncing behaviour, so that not every key stroke actually leads to a new call in the backend, relieving our precious servers a bit.

Conclusion

And with that I would like to conclude the learning outcome of this blog post.

Use flatMap for fast parallel calls, where ordering and interleaving cannot be guaranteed.

Use concatMap for strict natural order and no interleaving.

Use flatMapSequential for parallel calls with strict order and no interleaving by taking advantage of queueing up elements of slower inner publishers.

Use switchMap for situations, where only the current data is of importance and dropping previous data is explicitly desired.

Thanks for reading! Feel free to comment or message me, when you have questions or suggestions. You might be interested in the other posts published in the Digital Frontiers blog, announced on our Twitter account.

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co.

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co. KG (http://www.digitalfrontiers.de). Hier veröffentlichen wir zu Themen, die uns interessieren und bewegen.

Benedikt Jerat

Written by

Digital Frontiers — Das Blog

Dies ist das Blog der Digital Frontiers GmbH & Co. KG (http://www.digitalfrontiers.de). Hier veröffentlichen wir zu Themen, die uns interessieren und bewegen.