Understanding Flux in Java Reactive Programming
Reactive programming is becoming a go-to paradigm for building scalable, resilient, and responsive applications. In the Java ecosystem, Project Reactor — a foundation of the Spring WebFlux framework — introduces two key types: Mono and Flux.
If you haven’t read my previous blog on Mono, I recommend checking it out first for a better understanding of reactive programming fundamentals.
https://medium.com/@mshubham2002/mastering-reactive-programming-in-java-with-mono-and-flux-e6be16a75c74
In this blog post, we will deep dive into Flux, understand its purpose, common operations, and see how it helps build reactive systems.
🚀 What is Flux?
Flux is a Publisher that emits 0 to N items, followed by either completion or error signal.
@Test
public void testFlux(){
Flux<String> flux = Flux.just("Shubham","Arti","Shivam","Saurabh")
.log();
flux.subscribe(System.out::println);
}
/* o/p
2025-07-12T23:51:14.464+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription)
2025-07-12T23:51:14.464+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | request(unbounded)
2025-07-12T23:51:14.464+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onNext(Shubham)
Shubham
2025-07-12T23:51:14.479+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onNext(Arti)
Arti
2025-07-12T23:51:14.479+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onNext(Shivam)
Shivam
2025-07-12T23:51:14.479+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onNext(Saurabh)
Saurabh
2025-07-12T23:51:14.479+05:30 INFO 19780 --- [springboot] [ main] reactor.Flux.Array.1 : | onComplete()
here value are emitted over a period of time
*\🛠️ Core Mono Operators (with Examples)
In this section, we’ll cover the following operations:
- then()
- map()
- flatMap()
- empty()
- filter()
- fromIterbale()
- transform()
- defaultIfEmpty()
- switchIfEmpty()
- concat()
- concatWith()
- merge()
- mergeWith()
- zip() & zipWith()
then():- ignores all the values emitted by the current Flux and returns a Mono<Void>
map():- Transform Value (Sync) same like in mono
Use map() when transforming the emitted value synchronously.
@Test
public void testFluxWithMap(){
Flux<String> flux = Flux.just("Shubham","Arti","Shivam","Saurabh")
.map(x -> x.toUpperCase());
flux.subscribe(System.out::println);
}
/* o/p SHUBHAM
ARTI
SHIVAM
SAURABH
*\flatMap():- Transform Value (Async)
Get shubham mishra’s stories in your inbox
Join Medium for free to get updates from this writer.
Use flatMap() when the transformation returns another Flux, especially from async sources.
@Test
public void testFluxWithFlatMap(){
Flux<Object> flux = Flux.just("Shubham","Arti","Shivam","Saurabh")
.flatMap(x -> Flux.just(x.split("")));
flux.subscribe(System.out::println);
}
/* o/p S
h
u
b
h
a
m
A
r
t
i
S
h
i
v
a
m
S
a
u
r
a
b
h
*\empty():- create empty flux
@Test
public void testFluxWithEmpty(){
Flux<String> flux = Flux.empty();
flux.subscribe(System.out::println);
}filter():- Filters elements based on a condition.
@Test
public void testFluxWithFilter(){
Flux<String> flux = Flux.just("Shubham","Arti","Shivam","Saurabh")
.filter(x -> x.startsWith("S"));
flux.subscribe(System.out::println);
}
/* o/p Shubham
Shivam
Saurabh
*\fromIterbale():- create a Flux from any Iterable (e.g., List, Set, etc.).
@Test
public void testFluxWithFromIterable(){
List<String> fruits = List.of("Apple", "Banana", "Cherry", "Date");
Flux<String> flux = Flux.fromIterable(fruits);
flux.subscribe(System.out::println);
}transform():- to apply a reusable transformation function to a Flux stream. It allows you to compose logic and keep your reactive pipelines clean and modular.
@Test
public void testFluxWithTransform(){
Flux<String> flux = Flux.just("apple", "banana", "mango", "orange");
Function<Flux<String>, Flux<String>> functionInterface =(name)-> name.map(fruit -> fruit.toUpperCase())
.filter(f->f.contains("P"));
Flux<String> transformedFlux = flux.transform(functionInterface);
transformedFlux.subscribe(System.out::println);
}
/* o/p APPLE
*\defaultIfEmpty():- used to emit a default value when a Flux completes without emitting any items.
@Test
public void testFluxWithDefaultEmpty(){
Flux<String> flux = Flux.just("apple", "banana", "mango", "orange")
.filter(f->f.length()>10)
.defaultIfEmpty("No Fruits");
flux.subscribe(System.out::println);
}
#o/p No fruitsswitchIfEmpty():- is used when a Flux emits no items — instead of emitting a single default value like defaultIfEmpty(), it switches to a new Publisher (another Flux or Mono).
@Test
public void testFluxWithSwitchEmpty(){
Flux<String> flux = Flux.just("apple", "banana", "mango", "orange")
.filter(f->f.length()>10)
.switchIfEmpty(Flux.just("pinapple", "kiwi", "grape"));
flux.subscribe(System.out::println);
}
/*op
pinapple
kiwi
grape
*\
concat():- joins multiple Flux sources together sequentially — it waits for one to complete before subscribing to the next.
@Test
public void testFluxWithConcat(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange");
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape");
Flux<String> concatenated = Flux.concat(flux1, flux2);
concatenated.subscribe(System.out::println);
}
/*
apple
banana
mango
orange
pinapple
kiwi
grape
*\
concatWith():- you append one Publisher to another, ensuring sequential execution.
@Test
public void testFluxWithConcatWith(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange");
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape");
Flux<String> concatenated = flux1.concatWith(flux2);
concatenated.subscribe(System.out::println);
}
/*
apple
banana
mango
orange
pinapple
kiwi
grape
*\merge():- combines multiple Flux sources and emits their values as they arrive, concurrently (without waiting for one to finish before starting another).
@Test
public void testFluxWithMerge(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange");
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape");
Flux<String> concatenated = Flux.merge(flux1, flux2);
concatenated.subscribe(System.out::println);
}
/*
apple
banana
mango
orange
pinapple
kiwi
grape
*\mergeWith():- merges the current Flux with another Publisher (usually another Flux or Mono), concurrently.
It is just a cleaner alternative to Flux.merge(flux1, flux2).
@Test
public void testFluxWithMergeWith(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange");
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape");
Flux<String> concatenated = flux1.mergeWith(flux2);
concatenated.subscribe(System.out::println);
}
/*
apple
banana
mango
orange
pinapple
kiwi
grape
*\Combining Flux
zip() and zipWith()
Combines multiple Fluxand returns a new Flux with combined values but here elements up to the shortest Flux.:
@Test
public void testFluxWithZip(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange");
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape");
Flux<Tuple2<String, String>> zip = Flux.zip(flux1, flux2);
zip.subscribe(data->{
System.out.println("data: "+data);
System.out.println("data one: "+data.getT1());
System.out.println("data two: "+data.getT2());
});
}
/*
data: [apple,pinapple]
data one: apple
data two: pinapple
data: [banana,kiwi]
data one: banana
data two: kiwi
data: [mango,grape]
data one: mango
data two: grape
exmpample:- Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange"); // 4 items
Flux<String> flux2 = Flux.just("pinapple", "kiwi", "grape"); //3 itmes
here on zip flux1.zipWith(flux2) total items 6 orange will not included
*\
⚙️ Side-Effect Operators in Mono
side-effect operators in Flux are used to perform actions at various stages of the reactive pipeline without altering the data flowing through it. These are helpful for logging, debugging, metrics collection, or performing any non-functional operations.
- doOnNext()
- doOnSubscribe()
- doOnComplete()
- doOnCancel()
- doOnError()
doOnNext():- perform an action each time a value is emitted, without modifying the stream.
@Test
public void testFluxWithDoOnNext(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange")
.doOnNext(data-> System.out.println(data));
flux1.subscribe(data->{
System.out.println("data: "+data);
});
}
/*
apple
data: apple
banana
data: banana
mango
data: mango
orange
data: orange
*\
doOnSubscribe():- triggered when a subscriber subscribes to the Flux or Mono.
@Test
public void testFluxWithDoOnSubscribe(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange")
.doOnSubscribe(data-> System.out.println(data+" on subscribe"));
flux1.subscribe(data->{
System.out.println("data: "+data);
});
}
/*
reactor.core.publisher.FluxArray$ArraySubscription@48499739 on subscribe
data: apple
data: banana
data: mango
data: orange
*\
doOnComplete():- when the Flux successfully completes — meaning it emitted all items without errors.
@Test
public void testFluxWithDoOnComplete(){
Flux<String> flux1 = Flux.just("apple", "banana", "mango", "orange")
.doOnComplete(()-> System.out.println(" on complete"));
flux1.subscribe(data->{
System.out.println("data: "+data);
});
}
/*
data: apple
data: banana
data: mango
data: orange
on complete
*\
To convert mono to flux use mono.flux()
📚 GitHub Repository for Reference:
👉 https://github.com/iamshubhammishra/reactive-programming/blob/main/src/test/java/com/shubham/springboot/FluxApplicationTest.java








