DEV Community

Cover image for 🚀 Building Spring by Example V2: Completing Module 03 - Reactive Spring
Mujuzi Moses 
Mujuzi Moses 

Posted on

🚀 Building Spring by Example V2: Completing Module 03 - Reactive Spring

When I started learning Spring, I noticed something about many of the tutorials I found.

They often showed how to use a feature, but not always why it worked or when you should choose one approach over another.

I learn best by building things and documenting what I discover, so I started a project called spring-by-example-v2.

The idea is simple: instead of building one large application, I’m creating a collection of small, focused examples where each example explores a specific Spring concept with clear explanations, tests, and documentation.

I’m happy to say that I’ve now completed Module 03 - Reactive Spring! 🎉

This module was an opportunity to explore a different way of building applications with Spring. Rather than focusing only on traditional blocking operations, I explored reactive programming, asynchronous data processing, and how Spring WebFlux works with Project Reactor.

The module covers:

  • Project Reactor
  • Mono and Flux
  • Reactive Pipelines
  • Backpressure
  • Spring WebFlux
  • Reactive Controllers
  • Reactive Clients
  • Reactive Data Access
  • Reactive Error Handling
  • Reactive Testing

Understanding Mono and Flux

One of the first things I explored was the difference between Mono and Flux.

In Project Reactor, these are two fundamental types for representing asynchronous sequences of data.

A Mono represents a sequence that produces at most one item, while a Flux represents a sequence that can produce zero to many items.

For example:

Mono<String> greeting = Mono.just("Hello, Spring!");

Flux<String> greetings = Flux.just("Hello", "from", "Reactive Spring");
Enter fullscreen mode Exit fullscreen mode

These types don't simply represent values or collections. They represent publishers that can participate in a reactive pipeline.

That distinction helped me understand why reactive code often looks different from traditional Java code.

Building Reactive Pipelines

After learning the basics, I explored how reactive pipelines are composed.

Instead of processing data through a sequence of blocking method calls, Reactor allows operations to be chained together using operators such as map, filter, and flatMap.

For example:

Flux<String> names = Flux.just("Moses", "John", "Mary");

Flux<String> filteredNames = names
        .filter(name -> name.length() > 4)
        .map(String::toUpperCase);
Enter fullscreen mode Exit fullscreen mode

Here, the pipeline filters the names and transforms the remaining values.

Exploring these operators helped me understand how data flows through a reactive sequence and how individual operations can be composed into larger processing pipelines.

Understanding Backpressure

Backpressure was another important concept in this module.

A publisher may produce data faster than a subscriber can process it. Backpressure provides a mechanism through which demand can be communicated so that data processing can be better coordinated.

This introduced me to the relationship between publishers, subscribers, and demand in reactive streams.

It also helped me understand that reactive programming isn't simply about using asynchronous APIs. It involves thinking carefully about how data is produced, consumed, and managed throughout a pipeline.

Bringing Reactive Programming into Spring WebFlux

Once I had explored the Reactor fundamentals, I moved into Spring WebFlux.

I worked through reactive controllers, reactive clients, and reactive data access to understand how reactive programming fits into a Spring application.

One of the important distinctions I explored was that returning a Mono or Flux from a controller doesn't automatically make every operation in the application non-blocking.

The underlying operations matter too.

For example, a reactive controller that calls a blocking database driver can still introduce blocking work into the request-processing flow.

Understanding this distinction helped me appreciate why WebFlux works best when the application uses compatible non-blocking components throughout the relevant execution path.

Reactive Error Handling

Errors are an inevitable part of application development, and reactive pipelines introduce their own ways of handling them.

I explored how errors propagate through a pipeline and how Reactor operators can be used to recover from certain failures.

For example:

Mono<String> result = Mono.error(
        new IllegalStateException("Something went wrong")
).onErrorReturn("Fallback value");
Enter fullscreen mode Exit fullscreen mode

Instead of allowing the error to terminate the sequence without recovery, onErrorReturn provides a fallback value.

This helped me understand error handling as part of the reactive pipeline rather than treating it as something separate from the data-processing flow.

Testing Reactive Applications

One of my favourite parts of this module was reactive testing.

Testing reactive code requires more than checking the final value returned by a method. Depending on the behavior being tested, we may also need to verify emitted items, completion signals, errors, timing, and demand.

I explored StepVerifier from Project Reactor's testing utilities.

For example:

StepVerifier.create(Flux.just("Spring", "WebFlux", "Reactor"))
.expectNext("Spring")
.expectNext("WebFlux")
.expectNext("Reactor").verifyComplete();
Enter fullscreen mode Exit fullscreen mode

This test verifies the sequence of emitted values and confirms that the publisher completes successfully.

I also explored:

  • WebTestClient for testing reactive web endpoints.
  • Virtual-time testing for time-dependent reactive sequences without waiting for real delays.
  • Error testing for verifying how publishers behave when failures occur.
  • Backpressure testing for checking how publishers respond to subscriber demand.

These examples helped me see that reactive behavior can be tested explicitly rather than relying only on assumptions about how the pipeline should work.

What I Took Away from This Module

The biggest lesson from this module is that reactive programming is not just about replacing return types with Mono and Flux.

It's about understanding asynchronous sequences, composing operations, handling errors, managing demand, and choosing the right components for the execution model.

I also learned to pay more attention to the difference between writing reactive code and building an application that is genuinely non-blocking across its relevant processing path.

As with the previous modules, the goal wasn't to build one large application that uses every feature.

It was to create small, focused examples that make each concept easier to understand, test, and revisit later.

With Spring Data and Reactive Spring now complete, spring-by-example-v2 continues to grow as a practical learning resource covering different parts of the Spring ecosystem.

The project is open source, and I'm continuing to build it as I learn:

🔗 GitHub: https://gith.ub/dsAQrAU5

🔗 GitLab: https://gitl.ab/gfDEyGH3

Completing Module 03 - Reactive Spring feels like another important milestone in this journey. 🚀

There are still more Spring concepts to explore, more examples to build, and more things to learn.

One focused example at a time.

If you've worked with reactive programming in Spring, what was the biggest learning curve for you - understanding Reactor, handling backpressure, testing reactive pipelines, or knowing when to choose WebFlux over Spring MVC?

Java #SpringFramework #SpringBoot #ProjectReactor #SpringWebFlux #ReactiveProgramming #OpenSource #LearningInPublic #BackendDevelopment #SoftwareEngineering

Top comments (0)