Open In App

Cancel an Ongoing Flux in Spring WebFlux

Last Updated : 20 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Spring WebFlux is a reactive, non-blocking web framework for creating contemporary, scalable Java online applications. It is a component of the Spring Framework and implements Java reactive programming using the Reactor library.

Example of Cancel an Ongoing Flux in Spring WebFlux

Below is the Example of Cancel an Ongoing Flux in Spring WebFlux:

Java
import reactor.core.Disposable;
import reactor.core.publisher.Flux;

public class FluxCancellationExample {

    public static void main(String[] args) throws InterruptedException {
        // Create a Flux with delayed elements
        Flux<Integer> flux = Flux.range(1, 10)
                .delayElements(java.time.Duration.ofSeconds(1));

        // Subscribe to the Flux and get a Disposable
        Disposable disposable = flux.subscribe(
                i -> System.out.println("Received: " + i),
                e -> System.err.println("Error: " + e.getMessage()),
                () -> System.out.println("Flux completed")
        );

        // Wait for a while
        Thread.sleep(5000);

        // Dispose the Flux to cancel the ongoing subscription
        disposable.dispose();

        // After disposal, you won't receive further elements from the Flux
    }
}

Implementation to Cancel an Ongoing Flux in Spring WebFlux

Below are the implementation steps to Cancel an Ongoing Flux in Spring WebFlux.

Step 1: Create a Flux

Make an instance of Flux to represent the data stream. As an illustration:

Flux<Integer> flux = Flux.range(1, 10)
.delayElements(Duration.ofSeconds(1));

Step 2: Subscribe to the Flux

Get a Disposable object by subscribing to the Flux. We can terminate the active subscription using this item.

Java
// Creating a disposable to manage the subscription
Disposable disposable = 

    // Subscribing to the Flux with three lambda expressions:
    flux.subscribe(
        
        // Consumer for onNext event - printing the received item
        i -> System.out.println("Received: " + i),
        
        // Consumer for onError event - printing the error message
        e -> System.err.println("Error: " + e.getMessage()),
        
        // Runnable for onComplete event - printing a completion message
        () -> System.out.println("Flux completed")
    );

Step 3: Add Maven Dependencies

Consider a very basic scenario where some sensor data is received as a Flux, and we wish to use the different WebFlux options to cancel the data emission based on the subscription. It is now time to list these dependencies in the project.

<dependencies>
<!-- Spring Boot WebFlux Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Project Reactor Spring Integration -->
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>
<version>${reactor-spring.version}</version>
</dependency>
<!-- Reactor Test for Testing -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Step 4: Use dispose() Method

Let’s build up an example where we have an integer flux that emits numbers between 1 and 10 after a 1-second delay. In order to publish the data on the console, we will subscribe to the flux. The thread will then be put to sleep for 5 milliseconds before dispose() is called.

Java
import org.junit.jupiter.api.Test;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;

class FluxDisposalTest {

    @Test
    void giveAnOnGoingFlux_whenDispose_thenCancelsFluxExplicitly() throws InterruptedException {
        // Create a flux with delayed elements
        Flux<Integer> flux = Flux.range(1, 10)
                .delayElements(Duration.ofSeconds(1));

        // Counter for received elements
        AtomicInteger count = new AtomicInteger(0);

        // Subscribe to the flux and handle elements and errors
        Disposable disposable = flux.subscribe(
                i -> {
                    System.out.println("Received: " + i);
                    count.incrementAndGet();
                },
                e -> System.err.println("Error: " + e.getMessage())
        );

        // Wait for a while
        Thread.sleep(5000);

        // Dispose the flux and check if it's disposed
        System.out.println("Will Dispose the Flux Next");
        disposable.dispose();
        if (disposable.isDisposed()) {
            System.out.println("Flux Disposed");
        }

        // Assert the expected count of received elements
        assertEquals(4, count.get());
    }
}

Step 5: Test the Cancellations of flux

As soon as the flux is cancelled, we will output the relevant signals to make the test simpler. But in practice, this phase may handle any resource cleaning, such as disconnecting a connection, for example. As part of the post-cancellation cleaning, let’s quickly verify that our desired strings are printed when the Flux is cancelled:

Java
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.SignalType;

import java.io.PrintStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

// Class to test Flux cancellation behavior
class FluxCancellationTest {

    // Test to verify the cancellation message is printed when the Flux is canceled
    @Test
    void whenFluxCanceled_thenPrintCancellationMessage() throws InterruptedException {
        // Creating a list to store results
        List<Integer> result = new ArrayList<>();
        // Creating a mock PrintStream
        PrintStream mockPrintStream = mock(PrintStream.class);
        // Setting the mock PrintStream as System.out
        System.setOut(mockPrintStream);

        // Creating a Flux emitting sensor data at intervals
        Flux<Integer> sensorData = Flux.interval(Duration.ofMillis(200))
                // Handling cancellation
                .doOnCancel(() -> System.out.println("Flux Canceled"))
                // Handling completion or error
                .doFinally(signalType ->
                        System.out.println("Flux Completed because of " +
                                (signalType == SignalType.CANCEL ? "Cancellation" : "Error or Completion")))
                // Mapping the emitted values
                .map(i -> ThreadLocalRandom.current().nextInt(1, 2002))
                // Storing the emitted values
                .doOnNext(result::add);

        // Subscribing to the Flux
        Disposable subscription = sensorData.subscribe();
        // Waiting for some time
        Thread.sleep(2000);

        // Disposing the subscription
        subscription.dispose();
        // Capturing printed messages
        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
        // Verifying that the expected messages were printed
        verify(mockPrintStream, times(2)).println(captor.capture());

        // Asserting the captured messages
        assertThat(captor.getAllValues()).contains("Flux Canceled", "Flux Completed due to Cancellation");
    }
}
  • The FluxCancellationTest JUnit test case demonstrates how to use Spring WebFlux to terminate an active Flux subscription in a reactive environment.
  • In order to conduct the test, a faked PrintStream for recording console output must be set up, and a Flux stream that periodically emits random numbers must be created and subscribed to.
  • The test confirms that the console prints the relevant messages upon cancellation of the subscription.


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads