Enterprise Java

Spring Reactive switchIfEmpty() Example

The switchIfEmpty operator in Spring Reactive allows developers to provide an alternative publisher that is subscribed to when the source publisher completes without emitting any items. Let us delve into understanding how the spring reactive switchIfEmpty() operator works, exploring its behavior and impact on reactive streams.

1. Introduction

Spring Reactive is part of the Spring Framework, designed to facilitate the development of asynchronous, non-blocking applications using the reactive programming paradigm. This approach allows developers to create systems that are highly responsive, resilient, and scalable. The core building blocks of Spring Reactive include Flux and Mono, which represent sequences of 0 to N and 0 to 1 items, respectively. For more information on Spring Reactive, you can visit the official documentation: Spring WebFlux Documentation.

1.1 Pros

The advantages of using Spring Reactive include:

  • Asynchronous Processing: It enables handling requests without blocking threads, improving resource utilization.
  • Backpressure Support: It provides built-in mechanisms to handle varying rates of data production and consumption.
  • Improved Scalability: Applications can scale more efficiently, especially under heavy load, due to non-blocking I/O operations.
  • Integration with Reactive Streams: Spring Reactive integrates seamlessly with the Reactive Streams API, allowing for a standardized way to handle asynchronous data flows.
  • Simplified Error Handling: It offers powerful error-handling capabilities within reactive streams.

2. Use of switchIfEmpty() and Defer()

The switchIfEmpty() operator is a powerful tool for handling situations where a reactive stream emits no items. This operator allows developers to specify an alternative publisher that should be subscribed to if the source publisher completes without emitting any items.

The defer() operator can be used to create a new publisher each time it is subscribed to, ensuring that the fallback logic is executed only when necessary.

2.1 Code Example

Below is a complete Spring Boot application demonstrating the use of switchIfEmpty() in conjunction with defer():

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

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

@Service
class ItemService {
  public Flux<String> getItems() {
    return Flux
        .empty() // Simulating an empty response
        .switchIfEmpty(Flux.defer(() -> Flux.just("Default Item")));
  }
}

@RestController
class ItemController {
  private final ItemService itemService;

  public ItemController(ItemService itemService) {
    this.itemService = itemService;
  }

  @GetMapping("/items")
  public Flux<String> fetchItems() {
    return itemService.getItems();
  }
}

The code begins by importing the necessary Spring Boot and Reactor libraries. The @SpringBootApplication annotation is used to denote the main class of the Spring application, SpringReactiveDemoApplication. This annotation combines three functionalities: @Configuration, @EnableAutoConfiguration, and @ComponentScan. It indicates that this class is the primary configuration class for the Spring application.

Inside the SpringReactiveDemoApplication class, the main method serves as the entry point of the application. It calls SpringApplication.run(), which bootstraps the Spring application, starting the embedded web server and initializing the application context.

The code also defines an ItemService class, annotated with @Service, indicating that it is a service component in the Spring context. This class contains a method named getItems that returns a Flux of strings. The method simulates fetching items from a data source by returning an empty Flux. If no items are found, the switchIfEmpty() operator is used to provide a fallback value, returning a Flux that emits a default item, “Default Item”.

Next, the ItemController class is defined, which is annotated with @RestController. This annotation indicates that the class handles HTTP requests and returns data directly to the client. The ItemController has a dependency on ItemService, which is injected via its constructor. The @GetMapping("/items") annotation maps HTTP GET requests to the fetchItems method, allowing clients to retrieve items from the service. This method calls getItems from ItemService and returns the resulting Flux of strings.

Overall, this code demonstrates a simple Spring Reactive application that utilizes the reactive programming model to handle empty streams and provide default responses through the use of the switchIfEmpty() operator.

2.2 Testing

Testing the functionality of switchIfEmpty() can be accomplished using JUnit and Reactor Test. Here’s an example test case for the ItemService class:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.test.StepVerifier;

@SpringBootTest
public class ItemServiceTest {
  @Autowired private ItemService itemService;

  @Test
  public void testGetItems() {
    StepVerifier.create(itemService.getItems())
        .expectNext("Default Item")
        .verifyComplete();
  }
}

2.3 Use Cases of switchIfEmpty()

2.3.1 Use Cases Without defer()

  • Empty Source: When querying a database that may return no results, switchIfEmpty() can provide a default message or object, ensuring that the consumer always receives a value.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyExample {
        public static void main(String[] args) {
            // Empty Source
            Flux<String> emptySource = Flux.empty();
    
            // Using switchIfEmpty
            emptySource.switchIfEmpty(Flux.just("Default Item"))
                       .subscribe(System.out::println); // Outputs: Default Item
        }
    }
    
  • Non-Empty Source: If a source emits data, switchIfEmpty() allows the application to continue processing the emitted items without invoking the fallback logic, maintaining performance.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyNonEmptyExample {
        public static void main(String[] args) {
            // Non-Empty Source
            Flux<String> nonEmptySource = Flux.just("Item 1", "Item 2");
    
            // Using switchIfEmpty
            nonEmptySource.switchIfEmpty(Flux.just("Default Item"))
                          .subscribe(System.out::println); // Outputs: Item 1, Item 2
        }
    }
    

2.3.2 Use Cases With defer()

  • Empty Source: Using defer() allows for the creation of a new fallback publisher each time the source is subscribed to, ensuring that the fallback logic is evaluated only when needed.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyDeferExample {
        public static void main(String[] args) {
            // Empty Source
            Flux<String> emptySource = Flux.empty();
    
            // Using switchIfEmpty with defer
            emptySource.switchIfEmpty(Flux.defer(() -> Flux.just("Default Item")))
                       .subscribe(System.out::println); // Outputs: Default Item
        }
    }
    
  • Non-Empty Source: Even when using defer() if the source emits data, the fallback publisher is ignored, allowing the system to avoid unnecessary resource allocation for the fallback logic.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyDeferNonEmptyExample {
        public static void main(String[] args) {
            // Non-Empty Source
            Flux<String> nonEmptySource = Flux.just("Item 1", "Item 2");
    
            // Using switchIfEmpty with defer
            nonEmptySource.switchIfEmpty(Flux.defer(() -> Flux.just("Default Item")))
                          .subscribe(System.out::println); // Outputs: Item 1, Item 2
        }
    }
    

2.3.3 Combined Use Cases

  • Dynamic Fallback Logic: Using defer() in conjunction with switchIfEmpty() can allow for dynamic generation of fallback values based on runtime conditions, making applications more flexible.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyDynamicExample {
        public static void main(String[] args) {
            // Dynamic Source
            Flux<String> dynamicSource = Flux.empty();
    
            // Using switchIfEmpty with defer to generate a dynamic fallback
            dynamicSource.switchIfEmpty(Flux.defer(() -> Flux.just("Dynamic Item: " + System.currentTimeMillis())))
                         .subscribe(System.out::println); // Outputs: Dynamic Item: [current timestamp]
        }
    }
    
  • Improved Resource Management: With defer(), applications can efficiently manage resources by deferring the creation of fallback publishers until necessary, particularly in high-load scenarios.
    import reactor.core.publisher.Flux;
    
    public class SwitchIfEmptyResourceManagementExample {
        public static void main(String[] args) {
            // Resource Management Source
            Flux<String> resourceManagementSource = Flux.empty();
    
            // Using switchIfEmpty with defer for resource management
            resourceManagementSource.switchIfEmpty(Flux.defer(() -> Flux.just("Managed Resource")))
                                    .subscribe(System.out::println); // Outputs: Managed Resource
        }
    }
    

3. Conclusion

The switchIfEmpty() operator in Spring Reactive provides a robust mechanism for handling empty streams, allowing developers to define fallback logic seamlessly. By combining it with defer(), you can ensure that alternative publishers are created on demand, improving resource management and overall application performance. This functionality is invaluable in reactive applications where data availability can be unpredictable.

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