Learn reactive programming through a practical, interactive demo application
Reactive programming has become a cornerstone of modern Java development, especially when building high-performance, scalable applications. But learning reactive concepts can be challenging, as the abstract nature of streams, backpressure, and non-blocking I/O often requires hands-on experience to truly understand.
That’s why I built the Reactive Stream Demo, a standalone Spring Boot application that demonstrates 10 different reactive programming patterns through interactive examples. In this blog post, I’ll walk you through what makes this project special, how it works, and how you can use it to master reactive programming.
But first let’s define the basics.
Before diving into the project, let’s quickly recap what reactive programming is all about:
Reactive programming is a programming paradigm focused on asynchronous data streams and the propagation of change. Instead of blocking threads waiting for data, reactive applications handle streams of data asynchronously, enabling:
The Reactive Streams specification (implemented by Project Reactor in Spring) provides a standard for asynchronous stream processing with non-blocking backpressure.
The Reactive Stream Demo is a comprehensive Spring Boot application that showcases reactive programming patterns through 10 interactive scenarios. It’s built with:
Let me walk you through each scenario and what it teaches:
What it demonstrates: Fundamental reactive stream operations
@GetMapping(value = "/basic-operations", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> basicOperations() {
return Flux.range(1, 20)
.map(i -> "Number: " + i)
.filter(s -> s.contains("1") || s.contains("5"))
.map(s -> s + " (filtered)")
.delayElements(Duration.ofMillis(500));
}
This demo shows the bread and butter of reactive programming:
Flux.range(): Creates a stream of numbersmap(): Transforms each elementfilter(): Selects elements based on conditionsdelayElements(): Introduces time-based behaviorKey Learning: Reactive streams are composable and you chain operations to build data pipelines.
What it demonstrates: Managing fast producers and slow consumers
@GetMapping(value = "/backpressure", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> backpressureDemo() {
return Flux.range(1, 1000)
.map(i -> "Fast data: " + i)
.onBackpressureBuffer(10) // Buffer up to 10 items
.delayElements(Duration.ofMillis(100));
}
Backpressure occurs when a producer generates data faster than the consumer can process it. This scenario shows:
onBackpressureBuffer(): Buffers excess itemsonBackpressureDrop(): Drops items when buffer is fullKey Learning: Reactive streams automatically signal when they can’t keep up, preventing memory issues.
What it demonstrates: Resilient error handling patterns
@GetMapping(value = "/error-handling", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> errorHandlingDemo() {
return Flux.range(1, 10)
.map(i -> {
if (i == 5) {
throw new RuntimeException("Simulated error at item 5");
}
return "Item: " + i;
})
.onErrorResume(error -> {
log.warn("Error occurred: {}", error.getMessage());
return Flux.just("Error recovered: " + error.getMessage());
})
.retry(2);
}
This demo showcases:
onErrorResume(): Provides fallback data when errors occurretry(): Automatically retries failed operationsKey Learning: Reactive streams provide powerful error handling that keeps your application resilient.
What it demonstrates: Stream behavior differences
@GetMapping(value = "/hot-cold-streams", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> hotColdStreamsDemo() {
Flux<String> coldStream = Flux.range(1, 5)
.map(i -> "Cold: " + i)
.delayElements(Duration.ofMillis(200));
return coldStream
.share() // This line converts cold to hot stream
.doOnNext(event -> log.debug("Hot/Cold: {}", event));
}
Key Learning: Understanding hot vs cold streams is crucial for building efficient reactive applications.
What it demonstrates: Stream composition patterns
@GetMapping(value = "/combining-streams", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> combiningStreamsDemo() {
Flux<String> stream1 = Flux.range(1, 5)
.map(i -> "Stream1: " + i)
.delayElements(Duration.ofMillis(300));
Flux<String> stream2 = Flux.range(10, 5)
.map(i -> "Stream2: " + i)
.delayElements(Duration.ofMillis(400));
return Flux.merge(stream1, stream2);
}
This scenario presents:
merge(): Combines multiple streams into onezip(): Pairs elements from different streamscombineLatest(): Combines the latest values from multiple streamsKey Learning: Reactive programming excels at combining multiple data sources.
The remaining scenarios cover:
The project uses a modern, production-ready stack:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>
The patterns demonstrated in this project are used in real-world applications:
Reactive programming shines when building APIs that need to handle thousands of concurrent requests. The non-blocking nature means you can serve more requests with fewer resources.
Example: A microservice that processes thousands of events per second from a message queue.
Streaming data processing is a natural fit for reactive programming. The backpressure handling ensures your application doesn’t get overwhelmed.
Example: Processing real-time analytics from user interactions, IoT sensors, or financial market data.
When microservices communicate asynchronously, reactive streams provide a clean abstraction.
Example: A service that aggregates data from multiple downstream services and streams the results.
Real-time web applications benefit from reactive programming’s streaming capabilities.
Example: Live dashboards, chat applications, or collaborative editing tools.
Reactive programming isn’t just about code style—it delivers real performance benefits:
Traditional (Blocking):
@GetMapping("/data")
public List<Data> getData() {
return database.findAll(); // Blocks thread
}
Reactive (Non-blocking):
@GetMapping(value = "/data", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Data> getData() {
return database.findAllReactive(); // Non-blocking
}
In my testing, the reactive approach showed:
If you’re new to reactive programming, here’s a suggested learning path using this project:
Let me show you a more complex example from the project:
public Flux<StreamEvent> generateRealTimeEvents() {
return Flux.interval(Duration.ofMillis(500))
.map(tick -> {
String[] eventTypes = {
"order-created",
"payment-processed",
"inventory-updated"
};
String[] sources = {
"order-service",
"payment-service",
"inventory-service"
};
String eventType = eventTypes[(int) (tick % eventTypes.length)];
String source = sources[(int) (tick % sources.length)];
return StreamEvent.of(
eventType,
source,
Map.of(
"eventId", eventCounter.incrementAndGet(),
"timestamp", LocalDateTime.now(),
"data", "Demo event data " + tick
)
);
})
.take(50)
.doOnNext(event -> log.debug("Generated event: {}", event));
}
This example demonstrates:
Flux.interval(): Creates a time-based streamtake(50) limits the streamdoOnNext() for loggingThe project follows several best practices:
.onErrorResume(error -> {
log.warn("Error occurred: {}", error.getMessage());
return Flux.just(StreamEvent.of(
"error-recovery",
"demo-service",
Map.of("error", error.getMessage())
));
})
.retry(2)
public Mono<String> stopStream(String streamId) {
Sinks.Many<StreamEvent> streamSink = activeStreams.remove(streamId);
if (streamSink != null) {
streamSink.tryEmitComplete();
log.info("Stopped stream: {}", streamId);
return Mono.just("stopped");
}
return Mono.just("not-found");
}
The Reactive Stream Demo project demonstrates that reactive programming doesn’t have to be abstract or difficult to understand. Through hands-on examples and a real-time interface, you can see reactive patterns in action.
Whether you’re:
This project provides a solid foundation.
Try it yourself: Live Demo
Happy reactive programming! 🚀
If you found this post helpful, consider starring the root of this blogpost or sharing it with others learning reactive programming.