Enterprise Java

Conditionals in Spring WebFlux Reactive Flow

Spring WebFlux is a part of the Spring Framework that enables developers to build reactive, non-blocking web applications. Unlike traditional Spring MVC, which uses a servlet-based blocking model, WebFlux leverages a reactive programming paradigm to handle asynchronous data streams efficiently. Conditional operators like map(), filter(), switchIfEmpty(), defaultIfEmpty(), flatMap(), firstOnValue(), zip(), and zipWhen() enable developers to manipulate and respond to data dynamically. Let us delve into understanding how to use Spring WebFlux reactive conditionals to handle data streams effectively in a reactive programming paradigm.

1. Introduction

Spring Webflux is built on the Project Reactor library and offers powerful APIs such as Flux and Mono for handling streams of data. Let’s explore the different conditional operators in Spring WebFlux Reactive, their practical use cases, and snippets to better understand how they enhance reactive programming.

MethodDescriptionUse CaseSnippet
map()Transforms each element of the stream into another object by applying a synchronous function.Used for simple transformations like formatting or mapping values.
Flux.just("John", "Jane").map(String::toUpperCase);
filter()Filters elements in the stream based on a condition or predicate.Used to retain elements that satisfy a specific condition.
Flux.just(1, 2, 3, 4).filter(num -> num % 2 == 0);
switchIfEmpty()Provides an alternative Publisher if the original Publisher is empty.Used to define a fallback data stream for empty results.
Flux.empty().switchIfEmpty(Flux.just("Fallback"));
defaultIfEmpty()Emits a default value if the stream is empty.Used when a single default value is required for an empty stream.
Flux.empty().defaultIfEmpty("Default Value");
flatMap()Transforms each element of the stream into a Publisher and then flattens the resulting Publishers into a single stream.Used for asynchronous transformations or to handle nested Publishers.
Flux.just(1, 2).flatMap(id -> Mono.just("User" + id));
firstOnValue()Returns the first emitted value that matches the condition (when available).Used to find the first element satisfying a condition in a stream.
Flux.just(2, 4, 5, 6).filter(num -> num % 2 != 0).next();
zip()Combines multiple Publishers into a single Publisher that emits Tuples of combined elements.Used to combine streams and work with related data from multiple sources.
Flux.zip(Flux.just("A"), Flux.just(1));
zipWhen()Combines streams based on a condition using another asynchronous Publisher.Used for a conditional combination of multiple Publishers.
Flux.just(1, 2).zipWhen(num -> Mono.just(num * 10));

Spring WebFlux is ideal for applications that require high scalability, low latency, and efficient resource utilization.

2. Code Example

Here’s a complete Spring Boot application demonstrating various methods in a Spring WebFlux context.

2.1 Creating a main class

package com.example.webfluxdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebfluxDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebfluxDemoApplication.class, args);
    }
}

This is the main class of the Spring Boot application. It contains the main() method, which is the entry point for the application. The class is annotated with @SpringBootApplication, enabling component scanning, auto-configuration, and other Spring Boot features.

2.2 Creating a controller class

package com.example.webfluxdemo.controller;

import com.example.webfluxdemo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class DemoController {

    @Autowired
    private DemoService demoService;

    @GetMapping("/map")
    public Flux<String> demoMap() {
        return demoService.applyMap();
    }

    @GetMapping("/filter")
    public Flux<Integer> demoFilter() {
        return demoService.applyFilter();
    }

    @GetMapping("/switch-if-empty")
    public Flux<String> demoSwitchIfEmpty() {
        return demoService.applySwitchIfEmpty();
    }

    @GetMapping("/default-if-empty")
    public Flux<String> demoDefaultIfEmpty() {
        return demoService.applyDefaultIfEmpty();
    }

    @GetMapping("/flatmap")
    public Flux<String> demoFlatMap() {
        return demoService.applyFlatMap();
    }

    @GetMapping("/side-effects")
    public Flux<String> demoSideEffects() {
        return demoService.applySideEffects();
    }

    @GetMapping("/first-on-value")
    public Mono<Integer> demoFirstOnValue() {
        return demoService.applyFirstOnValue();
    }

    @GetMapping("/zip")
    public Flux<String> demoZip() {
        return demoService.applyZip();
    }
}

The DemoController class is a REST controller that exposes multiple endpoints to demonstrate different WebFlux methods. It uses the @RestController annotation to indicate that it handles HTTP requests. Each method in this class corresponds to a specific WebFlux operator, such as map(), filter(),
switchIfEmpty(), and so on. The methods call the respective service methods from the DemoService class and return the results as reactive streams.

2.3 Creating a service class

package com.example.webfluxdemo.service;

import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;

@Service
public class DemoService {

    public Flux<String> applyMap() {
        return Flux.just("John", "Doe", "Smith")
                .map(name -> name.equals("Doe") ? name.toUpperCase() : name);
    }

    public Flux<Integer> applyFilter() {
        return Flux.just(1, 2, 3, 4, 5)
                .filter(num -> num % 2 == 0);
    }

    public Flux<String> applySwitchIfEmpty() {
        return Flux.empty()
                .switchIfEmpty(Flux.just("No data available", "Try again later"));
    }

    public Flux<String> applyDefaultIfEmpty() {
        return Flux.empty()
                .defaultIfEmpty("Default Value");
    }

    public Flux<String> applyFlatMap() {
        return Flux.just(1, 2, 3)
                .flatMap(this::getUserNameById);
    }

    private Mono<String> getUserNameById(Integer id) {
        return Mono.just("User" + id);
    }

    public Flux<String> applySideEffects() {
        return Flux.just("Start", "Processing", "End")
                .doOnNext(log -> System.out.println("Log: " + log));
    }

    public Mono<Integer> applyFirstOnValue() {
        return Flux.just(2, 4, 5, 6)
                .filter(num -> num % 2 != 0)
                .next();
    }

    public Flux<String> applyZip() {
        Flux<String> names = Flux.just("Alice", "Bob");
        Flux<Integer> ages = Flux.just(25, 30);
        return Flux.zip(names, ages)
                .map(tuple -> tuple.getT1() + " is " + tuple.getT2() + " years old");
    }
}

The DemoService class contains the core logic for each WebFlux method. It is annotated with @Service, indicating that it is a service component in the application. The methods in this class demonstrate the usage of various WebFlux operators such as:

  • applyMap(): Transforms data using the map() operator.
  • applyFilter(): Filters elements based on a condition using the filter() operator.
  • applySwitchIfEmpty(): Provides an alternate stream when the original stream is empty.
  • applyDefaultIfEmpty(): Emits a default value when the stream is empty.
  • applyFlatMap(): Performs asynchronous transformations using the flatMap() operator.
  • applySideEffects(): Demonstrates side-effects using the doOnNext() operator.
  • applyFirstOnValue(): Finds the first value matching a condition.
  • applyZip(): Combines two streams into one using the zip() operator.

2.4 Creating a model class

package com.example.webfluxdemo.model;

public class User {
    private Integer id;
    private String name;

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    // Getters and Setters
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The User class is a simple POJO (Plain Old Java Object) that represents a user entity with two properties: id and name. It contains a parameterized constructor for initializing the fields, along with standard getters and setters for accessing and modifying the properties. Although this class is not used in every example, it serves as a base model for scenarios where we need to work with structured data.

2.5 Creating the properties file

server.port=8080
spring.main.web-application-type=reactive

This configuration file contains key-value pairs for the application settings. In this example, the file sets the application to run on port 8080 and configures the application to use the reactive web application type. These settings ensure the WebFlux application runs as expected.

3. Run the Application and trigger endpoints

To run the Spring Boot WebFlux application, follow these steps:

  • Ensure you have Java 17+ and Maven installed on your system.
  • Navigate to the project directory.
  • Build and run the application using the command: mvn spring-boot:run.
  • Once the application is running, it will be available at http://localhost:8080.

You can trigger the application endpoints by accessing the following URLs in a browser or using tools like
Postman or cURL:

  • Map Example: http://localhost:8080/map
  • Filter Example: http://localhost:8080/filter
  • SwitchIfEmpty Example: http://localhost:8080/switch-if-empty
  • DefaultIfEmpty Example: http://localhost:8080/default-if-empty
  • FlatMap Example: http://localhost:8080/flatmap
  • Side Effects Example: http://localhost:8080/side-effects
  • FirstOnValue Example: http://localhost:8080/first-on-value
  • Zip Example: http://localhost:8080/zip

Each endpoint demonstrates a specific WebFlux method, and the responses will appear as JSON or plain text.

4. Conclusion

In conclusion, Spring WebFlux provides a powerful and flexible toolkit for building reactive, non-blocking applications. The methods demonstrated in this article, such as map(), filter(), switchIfEmpty(), defaultIfEmpty(), flatMap(), firstOnValue(), zip(), and zipWhen(), enable developers to effectively manipulate and combine data streams in various scenarios. By understanding and using these methods appropriately, you can create applications that are highly performant, responsive, and maintainable. Whether it’s transforming data, handling empty streams, performing asynchronous operations, or combining multiple data sources, Spring WebFlux equips you with the tools needed for modern reactive programming.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button