I’ve been working with Uni Mutiny for reactive programming in Java and noticed that both chain
and flatMap
seem to serve similar purposes. From what I understand, both methods are used to transform the item emitted by a Uni into another Uni, effectively chaining or composing asynchronous operations.
For example, consider the following code snippets:
Using flatMap:
Uni<String> uni1 = Uni.createFrom().item("Hello");
Uni<String> result = uni1.flatMap(item -> Uni.createFrom().item(item + " World"));
result.subscribe().with(System.out::println); // Output: "Hello World"
Using chain:
Uni<String> uni1 = Uni.createFrom().item("Hello");
Uni<String> result = uni1.chain(item -> Uni.createFrom().item(item + " World"));
result.subscribe().with(System.out::println); // Output: "Hello World"
Both snippets produce the same result, and the behaviour seems identical. The only difference I can see is that chain appears to be more explicit about the intent of chaining or sequencing operations, while flatMap is more general-purpose.
My Question:
chain
and flatMap
?Yes, there is, although it’s small:
flatMap
is used when the new Uni
depends on the result of the previous one.
chain
has two variants:
The Supplier variant (chain(Supplier<Uni<T>>
) ignores the previous result and simply executes the next Uni
.
The Function variant (chain(Function<T, Uni<R>> mapper)
) is identical to flatMap
, as it uses the previous result.
Example:
// flatMap — the new Uni depends on the item
Uni<User> user = Uni.createFrom().item("123")
.flatMap(id -> fetchUserFromDatabase(id));
// chain (Supplier variant) — just executes the next action
Uni<Void> action = Uni.createFrom().item("123")
.chain(() -> sendAnalyticsEvent());
// chain (Function variant) — completely identical to flatMap
Uni<User> user2 = Uni.createFrom().item("123")
.chain(id -> fetchUserFromDatabase(id));
In reality, only chain(Supplier<Uni<T>>)
differs from flatMap
, as it does not pass the previous result.
Example:
UUni<String> uni = Uni.createFrom().item("Hello");
// flatMap uses the item
uni.flatMap(item -> fetchData(item));
// chain with Function behaves the same as flatMap
uni.chain(item -> fetchData(item));
// chain with Supplier — just executes the next Uni without the item
uni.chain(() -> fetchData("DefaultValue"));
Previously, I mistakenly stated that chain
handles null
better than flatMap
.
This is incorrect: both methods will throw an error if null
is returned.
Mutiny introduced chain
for code readability:
If you use the previous result → Use flatMap
or chain(Function)
.
If the previous result is not needed → Use chain(Supplier)
, because it clearly indicates that we are just triggering a new Uni
.
Other reactive libraries (Reactor
, RxJava
) do not make this distinction, but Mutiny added it to make the code clearer.
Use flatMap
or chain(Function)
if you need to process the result.
Use chain(Supplier)
if you just need to execute an action without using the previous result.
Simple rule of thumb:
Need to work with the previous value? → flatMap
or chain(Function)
Just executing the next action? → chain(Supplier)
Yes. flatMap
comes from functional programming and reactive libraries. Mutiny introduced chain
to make code more readable when you don’t need the data and just want to execute the next step.