Spring WebFlux Functional Endpoints
September 27, 2020
This page will walk through Spring WebFlux functional endpoints example. Spring WebFlux application can be created using annotated controller and functional web programming (WebFlux.fn). In functional web programming, we create functional endpoints to serve the HTTP requests. The functional programming model in Spring WebFlux is lightweight which uses functions to route and handle the requests. The Spring WebFlux functional programming (WebFlux.fn) model and annotation-based programming model, both run on the same reactive core foundation.
The WebFlux.fn uses
HandlerFunction
and RouterFunction
API. The HandlerFunction
is a function that takes ServerRequest
and returns delayed ServerResponse
. The RouterFunction
routes an incoming request to a handler function. The RouterFunction
is a function that takes ServerRequest
and returns a delayed HandlerFunction
.
Spring provides
@EnableWebFlux
annotation to be added with @Configuration
. The @EnableWebFlux
enables WebFlux features for annotated controller and functional web programming model. In Spring Boot Application, @EnableWebFlux
is not needed because Spring Boot autoconfigures WebFlux features. If we want complete control of WebFlux features in our Spring Boot application then we should use @EnableWebFlux
.
On this page we will create Spring Boot WebFlux CRUD application using functional web programming model. By default Spring Boot uses Reactor as reactive library and Netty as server.
Contents
1. Technologies Used
Find the technologies being used in our example.1. Java 11
2. Spring 5.2.8.RELEASE
3. Spring Boot 2.3.2.RELEASE
4. Maven 3.5.2
2. HandlerFunction
SpringHandlerFunction
is a functional interface with handle
functional method. The role of HandlerFunction
is performing HTTP requested task and returning response.
Find the source code for Spring
HandlerFunction
.
@FunctionalInterface public interface HandlerFunction<T extends ServerResponse> { Mono<T> handle(ServerRequest request); }
handle
functional method accepts ServerRequest
and returns Mono<ServerResponse>
.
Find the handler function that accepts
ServerRequest
and returns Mono<ServerResponse>
as following.
@Component public class BookHandler { @Autowired private BookService bookService; public Mono<ServerResponse> getBookById(ServerRequest request) { int id = Integer.parseInt(request.pathVariable("id")); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(bookService.getBookById(id), Book.class); } }
1. Using lambda expression.
serverRequest -> bookHandler.getBookById(serverRequest)
bookHandler::getBookById
HandlerFunction
can be equated with the body of a @RequestMapping
method in the annotation-based programming model.
3. RouterFunction
a.RouterFunction
functional interface :
Spring
RouterFunction
is a functional interface with route
functional method. The role of RouterFunction
is routing incoming requests to specified handler function.
Find the source code for Spring
RouterFunction
.
@FunctionalInterface public interface RouterFunction<T extends ServerResponse> { Mono<HandlerFunction<T>> route(ServerRequest request); ------ }
route
functional method accepts ServerRequest
and returns Mono<HandlerFunction<T>>
. The RouterFunction
has some defaults methods that are and
, andNest
, andOther
, andRoute
, filter
, accept
. To instantiate RouterFunction
, Spring provides RouterFunctions
class.
b.
RouterFunctions
class :
Spring
RouterFunctions
class is the central entry point to functional web framework. The RouterFunctions
class uses a builder-style API to build RouterFunction
with its RouterFunctions.Builder
inner class. The RouterFunctions.Builder
provides methods such as GET, POST, PUT, DELETE, HEAD, PATCH etc. These methods returns RouterFunctions.Builder
for further building the routes. They accept URL pattern, RequestPredicate
and HandlerFunction
as arguments whereas RequestPredicate
is optional.
Find method declarations of GET, POST, PUT, DELETE method from
RouterFunctions.Builder
doc.
1. The
GET
method adds a route to the specified handlerFunction that handles all HTTP GET requests matching the specified URL pattern and predicate.
RouterFunctions.Builder GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction)
POST
method adds a route to the specified handlerFunction that handles all HTTP POST requests matching the specified URL pattern and predicate.
RouterFunctions.Builder POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction)
PUT
method adds a route to the specified handlerFunction that handles all HTTP PUT requests matching the specified URL pattern and predicate.
RouterFunctions.Builder PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction)
DELETE
method adds a route to the specified handlerFunction that handles all HTTP DELETE requests matching the specified URL pattern and predicate.
RouterFunctions.Builder DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction)
Find the sample code to build the router.
@Configuration public class BookRouterConfig { @Bean public RouterFunction<ServerResponse> root(BookHandler bookHandler) { return RouterFunctions.route() .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::getBookById) .build(); } }
@Bean public RouterFunction<ServerResponse> root(BookHandler bookHandler) { return RouterFunctions.route() .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), serverRequest -> bookHandler.getBookById(serverRequest)) .build(); }
4. @EnableWebFlux
1. The@EnableWebFlux
is added with @Configuration
annotation. The @EnableWebFlux
imports the Spring WebFlux configuration from WebFluxConfigurationSupport
that enables use of annotated controllers and functional endpoints.
@Configuration @EnableWebFlux public class BookRouterConfig { ------ }
@EnableWebFlux
by implementing WebFluxConfigurer
interface and overriding its default methods such as configureHttpMessageCodecs()
, configurePathMatching()
, configureContentTypeResolver()
, configureArgumentResolvers()
etc.
@Configuration @EnableWebFlux public class MyConfiguration implements WebFluxConfigurer { @Override public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { ------ } }
@Configuration
but only one class should be annotated with @EnableWebFlux
.
4. In Spring Boot application,
@EnableWebFlux
is not required because Spring Boot applies WebFlux features by default.
5. In Spring Boot application, if we want to keep Spring Boot default WebFlux features and add some additional configurations, our
@Configuration
class should implement WebFluxConfigurer
but without @EnableWebFlux
.
6. In Spring Boot application, if we want complete control of Spring WebFlux, we can add
@EnableWebFlux
with @Configuration
annotation.
5. Mono and Flux
TheMono
and Flux
are Reactor
APIs from reactor.core.publisher
package. The Mono
completes successfully by emitting an element or with an error whereas the Flux
completes successfully by emitting 0 to N element or with an error.
The
Mono
has methods as flatMap
, flatMapMany
, just
, retry
etc.
The
Flux
has methods as flatMap
, flatMapIterable
, fromArray
, collectList
etc.
As we know that our handler function accepts
ServerRequest
and returns Mono<ServerResponse>
.
public Mono<ServerResponse> getBook(ServerRequest request) { ------ }
ServerRequest
and prepare Mono<ServerResponse>
to return by handler function.
1. Access request body as
Mono
.
Mono<Book> book = request.bodyToMono(Book.class);
Flux
.
Flux<Book> books = request.bodyToFlux(Book.class);
BodyExtractors
, too.
Mono<Book> book = request.body(BodyExtractors.toMono(Book.class)); Flux<Book> books = request.body(BodyExtractors.toFlux(Books.class));
Mono<MultiValueMap<String, Part> map = request.multipartData(); Flux<Part> parts = request.body(BodyExtractors.toParts());
Mono<ServerResponse> response = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(book));
6. WebClient
SpringWebClient
is a non-blocking reactive client API that performs HTTP requests. To send HTTP requests, the WebClient
has methods such as get()
, post()
, put()
, delete()
etc. Find the sample code to send HTTP GET request to server over given URL.
WebClient client = WebClient.create("http://localhost:8080"); public void getBookByIdDemo() { int id = 101; client.get() .uri("/books/" + id) .exchange() .flatMap(res -> res.bodyToMono(Book.class)) .subscribe(book -> System.out.println("GET: " + book), err -> System.out.println(err.getMessage())); }
7. Complete Example with Spring Boot
Here we will create a Spring Boot WebFlux application using functional programming model to serve HTTP GET, POST, PUT and DELETE requests. We will also create client usingWebClient
to send requests.
1. Server Code
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.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> </dependencies>
package com.concretepage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.reactive.config.EnableWebFlux; 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 @EnableWebFlux @ComponentScan("com.concretepage") public class BookRouterConfig { @Bean public RouterFunction<ServerResponse> root(BookHandler bookHandler) { return RouterFunctions.route() .GET("/books", bookHandler::getAllBooks) .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::getBookById) .POST("/add", RequestPredicates.contentType(MediaType.APPLICATION_JSON), bookHandler::addBook) .PUT("/update", RequestPredicates.contentType(MediaType.APPLICATION_JSON), bookHandler::updateBook) .DELETE("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::deleteBookById) .build(); } }
package com.concretepage; import java.net.URI; import org.springframework.beans.factory.annotation.Autowired; 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 { @Autowired private BookService bookService; public Mono<ServerResponse> getAllBooks(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(bookService.getAllBooks(), Book.class); } public Mono<ServerResponse> getBookById(ServerRequest request) { int id = Integer.parseInt(request.pathVariable("id")); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(bookService.getBookById(id), Book.class); } public Mono<ServerResponse> addBook(ServerRequest request) { return request.bodyToMono(Book.class) .flatMap(book -> bookService.addBook(book)) .flatMap(newBook -> ServerResponse.created(URI.create("/books/" + newBook.getId())) .contentType(MediaType.APPLICATION_JSON) .build()); } public Mono<ServerResponse> updateBook(ServerRequest request) { return request.bodyToMono(Book.class) .flatMap(book -> bookService.updateBook(book)) .flatMap(modBook -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(modBook))); } public Mono<ServerResponse> deleteBookById(ServerRequest request) { return bookService.deleteBookById(Integer.parseInt(request.pathVariable("id"))) .flatMap(val -> { if (val == true) { return ServerResponse.noContent().build(); } return ServerResponse.notFound().build(); }); } }
package com.concretepage; import java.util.stream.Stream; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Service public class BookService { public Flux<Book> getAllBooks() { return Flux.fromStream(Stream.of(new Book(201, "Python"), new Book(202, "HTML"))); } public Mono<Book> getBookById(int id) { return Mono.just(new Book(id, "Java")); } public Mono<Book> addBook(Book book) { return Mono.just(new Book(102, book.getName())); } public Mono<Book> updateBook(Book book) { return Mono.just(new Book(book.getId(), book.getName() +" - updated")); } public Mono<Boolean> deleteBookById(int id) { return Mono.just(true); } }
package com.concretepage; public class Book { private int id; private String name; public Book() {} public Book(String name) { this.name = name; } public Book(int id, String name) { this.id = id; this.name = name; } //Sets and Gets @Override public String toString() { return getId() + ", " + getName(); } }
2. Client Code
BookWebClient.java
package com.concretepage; import org.springframework.web.reactive.function.client.WebClient; public class BookWebClient { private WebClient client = WebClient.create("http://localhost:8080"); public void getAllBooksDemo() { client.get() .uri("/books") .exchange() .flatMapMany(res -> res.bodyToFlux(Book.class)) .collectList() .subscribe(books -> books.forEach(b -> System.out.println(b))); } public void getBookByIdDemo() { int id = 101; client.get() .uri("/books/" + id) .exchange() .flatMap(res -> res.bodyToMono(Book.class)) .subscribe(book -> System.out.println("GET: " + book), err -> System.out.println(err.getMessage())); } public void addBookDemo() { client.post() .uri("/add") .bodyValue(new Book("Spring")) .exchange() .subscribe(res -> System.out.println("POST: " + res.statusCode() + ", " + res.headers().asHttpHeaders().getLocation())); } public void updateBookDemo() { client.put() .uri("/update") .bodyValue(new Book(103, "Android")) .exchange() .flatMap(res -> res.bodyToMono(Book.class)) .subscribe(book -> System.out.println("PUT: " + book)); } public void deleteBookByIdDemo() { int id = 104; client.delete() .uri("/books/" + id) .exchange() .subscribe(res -> System.out.println("DELETE: " + res.statusCode())); } }
3. Run Application
Application.java
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); BookWebClient bwc = new BookWebClient(); bwc.getAllBooksDemo(); bwc.getBookByIdDemo(); bwc.addBookDemo(); bwc.updateBookDemo(); bwc.deleteBookByIdDemo(); } }
mvn spring-boot:run

8. References
Web on Reactive StackSpring WebFlux Auto-configuration
Mono
Flux