Spring WebFlux Controller Example
September 23, 2020
This page will walk through Spring WebFlux controller example. Spring WebFlux application can be created using two programming models i.e. Annotated Controllers and Functional Endpoints. In annotation based WebFlux programming, we create controller using @Controller
and @RestController
in the same way we do in Spring Web MVC. Our Java Configuration class should be annotated with @EnableWebFlux
.
Here on this page we will create Spring Boot WebFlux application based on annotated controller that will serve HTTP GET, POST, PUT and DELETE requests. By default Spring Boot uses Reactor as reactive library and Netty as server.
Contents
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
Maven Dependency
Find the Maven dependencies for Spring WebFlux application.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>
Use @EnableWebFlux
To create annotated controller and functional endpoints we should add the@EnableWebFlux
annotation to @Configuration
class. The @EnableWebFlux
imports the Spring WebFlux configuration from WebFluxConfigurationSupport
class.
AppConfig.java
package com.concretepage; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.config.EnableWebFlux; @Configuration @EnableWebFlux @ComponentScan("com.concretepage") public class AppConfig { }
Create Controller using Annotation
Here we will create Spring WebFlux annotated controller. The controller can be annotated with@Controller
and @RestController
annotations. For request mapping, we need to use @RequestMapping
annotation to map requests with controller methods. Requests can be matched with various attributes that are URL, HTTP method, request parameters, headers, and media types. The @RequestMapping
can be used at class level as well as method level. We can also use its shortcut variants that are @GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
etc. Find the sample code to create controller methods.
@GetMapping("/books") @ResponseStatus(HttpStatus.OK) public Mono<List<Book>> getAllBooks() { return bookService.getAllBooks(); }
Mono
is a reactive stream publisher API that completes successfully by emitting an element. The @ResponseStatus
sets the HTTP status code to return.
We can also use
ResponseEntity
to return body and status code.
@GetMapping("/books") public Mono<ResponseEntity<List<Book>>> getAllBooks() { return bookService.getAllBooks() .map(list -> new ResponseEntity<List<Book>>(list, HttpStatus.OK)); }
BookController.java
package com.concretepage; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; @RestController public class BookController { @Autowired private BookService bookService; @GetMapping(value="/books", produces = { MediaType.APPLICATION_JSON_VALUE }) public Mono<ResponseEntity<List<Book>>> getAllBooks() { return bookService.getAllBooks() .map(list -> new ResponseEntity<List<Book>>(list, HttpStatus.OK)); } @GetMapping("/books/{id}") public Mono<ResponseEntity<Book>> getBookById(@PathVariable("id") Integer id) { return bookService.getBookById(id) .map(book -> new ResponseEntity<Book>(book, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @PostMapping(value = "/add", consumes = { MediaType.APPLICATION_JSON_VALUE }) public Mono<ResponseEntity<Void>> addBook(@RequestBody Book book, UriComponentsBuilder builder) { return bookService.addBook(book) .map(newBook -> { HttpHeaders headers = new HttpHeaders(); headers.setLocation(builder.path("/books/{id}").buildAndExpand(newBook.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); }); } @PutMapping(value = "/update", consumes = { MediaType.APPLICATION_JSON_VALUE }) public Mono<ResponseEntity<Book>> updateBook(@RequestBody Book book) { return bookService.updateBook(book) .map(modBook -> new ResponseEntity<Book>(modBook, HttpStatus.OK)); } @DeleteMapping("/books/{id}") public Mono<ResponseEntity<Void>> deleteBookById(@PathVariable("id") Integer id) { return bookService.deleteBookById(id) .map(val -> { if (val == true) { return new ResponseEntity<Void>(HttpStatus.NO_CONTENT); } return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); }); } }
Create Service
For the demo purpose we have created a service for CRUD operation.BookService.java
package com.concretepage; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @Service public class BookService { public Mono<List<Book>> getAllBooks() { List<Book> list = Arrays.asList(new Book(201, "Python"), new Book(202, "HTML")); return Mono.just(list); } public Mono<Book> getBookById(int id) { //return Mono.empty(); 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) { System.out.println("Book deleted with id " + 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(); } }
Create Client
SpringWebClient
is a non-blocking reactive client API that performs HTTP requests. Find our client code to send HTTP GET, POST, PUT and DELETE requests.
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() .flatMap(res -> res.bodyToMono(Book[].class)) .subscribe(books -> { for (Book b : books) { 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 createBookDemo() { 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())); } }
Run Application
Find the Main class to run the Spring Boot 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.createBookDemo(); bwc.updateBookDemo(); bwc.deleteBookByIdDemo(); } }
mvn spring-boot:run