In this article, you will learn to create a Functional endpoint in Spring WebFlux. In the previous article, I have introduced you to create an endpoint using the Annotations, now we will look into the pure functional programming way to achieve this.

1. Overview of Functional endpoint

Spring Functional WebFlux

An Http request initiated by a client app arrives at the Server (Netty/Undertow etc.). This request is forwarded to the RouterFunction to assign an appropriate HandlerFunction to serve the Http request. The RouterFunction is similar to @RequestMapping and the HandlerFunction is similar to the body of @RequestMapping method in the annotation-driven programming model.

2. Functional reactive endpoints

We will use the standard start.spring.io to generate the starter Spring-Boot-WebFlux project to implement functional endpoints. The most important dependency that you need is spring-boot-starter-webflux. You can use Spring WebFlux without the SpringBoot, but we will make use of Spring boot in all of our examples.

Step-1: Generate the project from start.spring.io

We will generate our Spring WebFlux project from start.spring.io, with the dependency spring-boot-starter-webflux. Other dependencies are for testing and ease of development. Use the link to generate the project, unzip it, and import it to your IDE.

XML
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
....
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
  <dependencies>
.....
Step-2: Create the handler functions in HelloHandler

As mentioned before, Handler functions are the ones which serves the actual endpoints. So, you need to write the logic inside the handler functions to serve these endpoints. We will create a class asHelloHandler with two methods. One will serve us the TEXT_PLAIN and other one to serve APPLICATION_STREAM_JSON content.

Java
package c.jbd.webflux;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;

@Component
public class HelloHandler {
  /**
   * Serves a plain_text
   */
  public Mono<ServerResponse> monoMessage(ServerRequest request) {
    return ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .body(
        Mono.just("Welcome to JstoBigdata.com"), String.class
      );
  }
  
 /**
   * Serves a JSON stream
   */
  public Mono<ServerResponse> fluxMessage(ServerRequest request) {
    return ServerResponse.ok()
      .contentType(MediaType.APPLICATION_STREAM_JSON)
      .body(
        Flux.just("Welcome ", "to ", "JstoBigdata.com")
          .delayElements(Duration.ofSeconds(1)).log(), String.class
      );
  }
}
Java
package c.jbd.webflux;

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

@SpringBootApplication
public class FunctionalAppConfig {
  public static void main(String[] args) {
    SpringApplication.run(FunctionalAppConfig.class, args);
  }
}
Step-3: Create the Router functions in HelloRouter

We have the logic in the HelloHandler to serve the response. Now, we will write our router function in HelloRouter class to bind the specific endpoints. Basically, every endpoint will be linked to the specific router function.

Java
package c.jbd.webflux;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class HelloRouter {

  @Bean
  public RouterFunction<ServerResponse> functionalRoutes(HelloHandler helloHandler) {
    return RouterFunctions
      .route(RequestPredicates.GET("/functional/mono")
        , helloHandler::monoMessage)
      .andRoute(RequestPredicates.GET("functional/flux")
        , helloHandler::fluxMessage);
  }
}

It is recommended to declare the Router Function class using @Configurations annotations, and the router functions as beans.

Step-4: Start the App and Test the endpoints

Start the app by running mvn spring-boot:run

To test the Mono endpoint, open the below url in the browser.

http://localhost:8080/functional/mono

You will be able to get the message, Welcome to JstoBigdata.com on the browser. There is nothing special here.

Similarly, to test the Flux endpoint, open the below url in browser and observe the output.

http://localhost:8080/functional/flux
Json stream Spring WebFlux

You can see that the message gets printed on the browser part by part. This is because our endpoint sends the message as a JSON stream part by part in after each second, with the help of a Flux stream.

3. Test the endpoints using WebTestClient

The spring-test module provides mock implementations of ServerHttpRequest, ServerHttpResponse, and ServerWebExchange. The WebTestClient is built on these mock request and response objects to provide support for testing WebFlux applications without an HTTP server. You can use the WebTestClient for end-to-end integration tests, too.

Java
package c.jbd.webflux;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

@SpringBootTest
@AutoConfigureWebTestClient //Important
public class FunctionalAppTest {

  @Autowired
  private WebTestClient webTestClient;
  private final String TEST_MESSAGE = "Welcome to JstoBigdata.com";

  @Test
  public void testMonoEndpoint() {
    Flux<String> msg$ = webTestClient.get()
      .uri("/functional/mono")
      .accept(MediaType.TEXT_PLAIN)
      .exchange()
      .expectStatus().isOk()
      .returnResult(String.class).getResponseBody()
      .log();

    StepVerifier.create(msg$)
      .expectNext(TEST_MESSAGE)
      .verifyComplete();
  }

  @Test
  public void testFluxEndpoint() {
    Flux<String> msg$ = webTestClient.get()
      .uri("/functional/flux")
      .accept(MediaType.APPLICATION_STREAM_JSON)
      .exchange()
      .expectStatus().isOk()
      .returnResult(String.class).getResponseBody()
      .log();
  
    StepVerifier.create(msg$)
      .expectNext(TEST_MESSAGE)
      .verifyComplete();
  }
}

Conclusion

I have given you a brief guide on creating functional endpoint in Spring WebFlux. We will explore much more in the upcoming articles. Reactive programming provides better performance with limited hardware as compared to the traditional Spring MVC with too many threads. I would suggest trying this framework to see if this fulfils your project needs. You can download the complete source code from Github.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|