Spring Reactive REST API
February 02, 2019
This page will walk through Spring Reactive REST API example. Spring WebFlux is Spring reactive-stack web framework introduced in Spring 5.0. Spring WebFlux is fully non-blocking, supports Reactive Streams back pressure, and runs on the servers such as Netty, Undertow, and Servlet 3.1+ containers. Spring WebFlux is introduced for non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources.
Reactor is the reactive library for Spring WebFlux whose operators are aligned with ReactiveX. Reactor provides following Publishers.
Mono: A Reactive Steams Publisher that emits 0 to 1 element or an error.
Flux: A Reactive Steams Publisher that emits 0 to N elements or an error.
Mono
and Flux
uses operators that support non-blocking back pressure. Reactor focuses on server-side Java and has been developed in collaboration with Spring.
Here on this page we will create Spring Reactive RESTful web services. We will create functional endpoints as well as annotated controllers for our REST application.
Contents
Technologies Used
Find the technologies being used in our example.1. Java 11
2. Spring 5.1.3.RELEASE
3. Spring Boot 2.1.1.RELEASE
4. JUnit 5
5. Maven 3.5.2
6. Eclipse 2018-09
Create Functional Endpoints
Spring functional programming is the alternative of annotation-based programming. Spring WebFlux includes WebFlux.fn, a lightweight functional programming. It uses functions to route and handle requests and contracts are designed for immutability. To handle the requests we create handler functions and to route the requests to handler functions, we create router.Here we will create a Spring Boot Reactive REST API example using functional programming. Find the project structure.

spring-boot-starter-webflux
dependency.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
ServerResponse
and returns delayed ServerResponse
as Mono<ServerResponse>
. Find our handler function.
BookHandler.java
package com.concretepage; import java.util.function.Predicate; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; @Component public class BookHandler { public Mono<ServerResponse> getBookById(ServerRequest request) { int id = Integer.parseInt(request.pathVariable("id")); Predicate<Book> predicate = b -> b.getId() == id; Book book = Book.getAllBooks().stream().filter(predicate).findFirst().get(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromObject(book)); } public Mono<ServerResponse> getAllBooks(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromObject(Book.getAllBooks())); } }
@RequestMapping
method in the annotation-based programming model.
To route the requests to handler function, we need to create router function using
RouterFunction
.
BookRouter.java
package com.concretepage; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; 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 BookRouter { @Bean public RouterFunction<ServerResponse> root(BookHandler bookHandler) { return RouterFunctions.route() .GET("/book/{id}", accept(MediaType.TEXT_PLAIN), bookHandler::getBookById) .GET("/allbooks", accept(MediaType.TEXT_PLAIN), bookHandler::getAllBooks) .build(); } }
@RequestMapping
annotation. RouterFunctions.route()
returns a builder to create router.
return RouterFunctions.route() .GET("/book/{id}", accept(MediaType.TEXT_PLAIN), bookHandler::getBookById) .build();
return RouterFunctions.route( RequestPredicates.GET("/book/{id}").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), bookHandler::getBookById );
Book.java
package com.concretepage; import java.util.Arrays; import java.util.List; public class Book { private int id; private String name; public Book(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } public static List<Book> getAllBooks() { return Arrays.asList( new Book(100, "Java Tutorials"), new Book(200, "Spring Tutorials"), new Book(300, "Angular Tutorials") ); } }
package com.concretepage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
http://localhost:8080/book/200 http://localhost:8080/allbooks

pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework</groupId> <artifactId>spring-reactive</artifactId> <version>0.1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> </parent> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Create Annotated Controllers
Spring WebFlux uses same annotation-based programming model as in Spring MVC using@Controller
or @RestController
annotations. Spring provides @EnableWebFlux
to be annotated at JavaConfig class level with @Configuration
annotation. @EnableWebFlux
annotation imports Spring WebFlux configuration from WebFluxConfigurationSupport
class in our configuration class.
Here we will create a Spring Boot Reactive REST API example using annotated controller. Find the JavaConfig used in our demo application.
AppConfig.java
package com.concretepage; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.config.EnableWebFlux; @EnableWebFlux @Configuration @ComponentScan("com.concretepage") public class AppConfig { }
BookController.java
package com.concretepage; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class BookController { @Autowired private BookService service; @GetMapping("/book/{id}") public Mono<Book> getBookById(@PathVariable("id") Integer id) { return service.getBookById(id); } @GetMapping("/allbooks") public Mono<List<Book>> getAllBooks() { return service.getAllBooks(); } }
BookService.java
package com.concretepage; import java.util.List; import java.util.function.Predicate; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service public class BookService { public Mono<Book> getBookById(int id) { Predicate<Book> predicate = b -> b.getId() == id; Book book = Book.getAllBooks().stream().filter(predicate).findFirst().get(); return Mono.just(book); } public Mono<List<Book>> getAllBooks() { return Mono.just(Book.getAllBooks()); } }
AbstractReactiveWebInitializer
If we are not using Spring Boot, we needAbstractReactiveWebInitializer
to deploy WAR to any Servlet 3.1 container. AbstractReactiveWebInitializer
class wraps an HttpHandler
with ServletHttpHandlerAdapter
and registers that as a Servlet. We create the app initializer class extending AbstractReactiveWebInitializer
as following.
WebAppInitializer.java
package com.concretepage; import org.springframework.web.server.adapter.AbstractReactiveWebInitializer; public class WebAppInitializer extends AbstractReactiveWebInitializer { @Override protected Class<?>[] getConfigClasses() { return new Class[] { AppConfig.class }; } }
Create Client with WebClient
Spring WebFlux provides a reactive, non-blockingWebClient
for HTTP requests. WebClient
has functional, fluent reactive API to create client. Find the client code to consume RESTful web service in our demo application.
BookWebClient.java
package com.concretepage; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; public class BookWebClient { private WebClient client = WebClient.create("http://localhost:8080"); public String fetchBookById() { Mono<ClientResponse> result = client.get() .uri("/book/200") .accept(MediaType.APPLICATION_JSON) .exchange(); return result.flatMap(res -> res.bodyToMono(String.class)).block(); } public String fetchAllBooks() { Mono<ClientResponse> result = client.get() .uri("/allbooks") .accept(MediaType.APPLICATION_JSON) .exchange(); return result.flatMap(res -> res.bodyToMono(String.class)).block(); } }
Application
class as following.
Application.java
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); BookWebClient bwc = new BookWebClient(); System.out.println(bwc.fetchBookById()); System.out.println("---------------------"); System.out.println(bwc.fetchAllBooks()); } }
{"id":200,"name":"Spring Tutorials"} --------------------- [{"id":100,"name":"Java Tutorials"},{"id":200,"name":"Spring Tutorials"},{"id":300,"name":"Angular Tutorials"}]
Test with WebTestClient
Spring Test provides non-blocking, reactiveWebTestClient
to connect to a server over HTTP. WebTestClient
uses reactive WebClient
internally to provide fluent API to verify responses. Find the test class used in demo application.
BookAppTest.java
package com.concretepage; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BookAppTest { @Autowired private WebTestClient webTestClient; @Test public void getBookByIdTest() { webTestClient .get().uri("/book/200") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody(String.class); } @Test public void getAllBooksTest() { webTestClient .get().uri("/allbooks") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody(List.class); } }
References
Spring WebFluxBuilding a Reactive RESTful Web Service