Spring WebFlux Controller Example

By Arvind Rai, 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.

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();
} 
The 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));
} 
Find the controller code of our demo application that will serve GET, POST, PUT and DELETE requests.
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);
	}	
} 
Book.java
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

Spring WebClient 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();		
	}
} 
To run the application, download the code and go to root directory of the project using command prompt and run the command.
mvn spring-boot:run 
Find the print screen of the output.
Spring WebFlux Controller Example

Reference

Web on Reactive Stack

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us