Spring Boot WebFlux example: Building Rest API

In this tutorial, we’re gonna build a Spring Boot WebFlux Rest API example – CRUD application that uses Spring Data Reactive (R2DBC) to interact with embedded database. You’ll know:

  • Overview of Reactive Programming and handling Blocking and Non-blocking requests
  • How to configure Spring Data Reactive, R2DBC to work with Database
  • How to define Data Models and Repository interfaces
  • Way to create Spring Rest Controller to process HTTP requests
  • Way to use Spring Data R2DBC to interact with Database

More Practice:
Spring WebFlux File upload example
Spring Boot R2DBC + MySQL example
Spring Boot R2DBC + PostgreSQL example
Spring Boot Reactive + MongoDB example
Spring Boot Thymeleaf CRUD example
Secure Spring Boot App with Spring Security & JWT Authentication
Spring Boot Rest XML example – Web service with XML Response
Spring Boot + GraphQL + MySQL example
Spring Boot Multipart File upload example
Spring Boot Pagination and Sorting example

Associations:
Spring Boot One To One example with JPA, Hibernate
Spring Boot One To Many example with JPA, Hibernate
Spring Boot Many to Many example with JPA, Hibernate

Fullstack:
Vue + Spring Boot example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
Angular 12 + Spring Boot example
Angular 13 + Spring Boot example
Angular 14 + Spring Boot example
Angular 15 + Spring Boot example
React + Spring Boot example


Reactive Programming overview

Reactive Programming is a programming paradigm that is commonly used in web and mobile development. It provides a non-blocking, asynchronous approach to handling data, which helps manage data flow and synchronization between components. It is supported by several frameworks and libraries (such as Project Reactor, RxJava, Spring WebFlux…).

How it works

The data flow in an application is considered as a stream of events and reacting to changes in the data by triggering a cascade of events. The basic building blocks of Reactive Programming are:

  • Observables: data sources that represent a stream of events. They emit items over time, and Subscribers can observe and react to these events.
  • Subscribers: components that consume events from Observables. They can perform some actions, such as updating the state of the system, in response to the events they receive.
  • Operators: functions that can be applied to Observables to manipulate the events they emit. Operator could be filter, transform,…

Advantages and Disadvantages

Reactive Programming provides a flexible and powerful approach to building modern, responsive, and scalable systems.

  • Scalability: enables systems to scale smoothly, even when dealing with large amounts of data or user traffic, by providing a non-blocking, asynchronous approach to handling data.
  • Responsiveness: emphasizes the design of systems that respond quickly to changes in the input data, leading to improved user experience and application responsiveness.
  • Resilience: provides tools and patterns for building fault-tolerant and self-healing systems, allowing them to gracefully handle and recover from failures.
  • Loose coupling: promotes the development of loosely coupled components, which are easier to maintain, test, and evolve over time.
  • Modularity: supports the development of modular and composable systems, where complex functionality can be built by combining simple, reusable building blocks.

Reactive Programming may not be the best fit for all projects or scenarios. You need to evaluate carefully the trade-offs before deciding to use it.
Here are some drawbacks:

  • Complexity: for developers who are unfamiliar with its concepts and techniques, Reactive Programming can add complexity to the code
  • Debugging difficulties: this can be challenging due to the asynchronous and event-driven nature of the code.
  • Learning curve: you need to have a different mindset and approach to programming, which can take time and effort.
  • Performance: comparing to traditional, synchronous approaches, performance will be impacted when adding abstractions of reactive frameworks and libraries.
  • Tool: tooling and development support may still be limited in some contexts.

Otherwise, some of these disadvantages above may be mitigated with experience and proper implementation.

Handle Blocking and Non-blocking requests

Blocking API requests are typically used in simple applications where:
– the processing time for each request is short
– the number of concurrent requests is low

The client sends a request to the API, and the API blocks all other requests until it completes processing the request and sends a response back to the client. Spring provides support for blocking API calls through its traditional servlet-based web framework, Spring MVC.

For high-performance systems, implementing non-blocking APIs is a better choice.

The client sends a request and immediately receives a response, allowing it to continue processing while the API continues to process the request in the background. This enables multiple requests to be processed concurrently without blocking each other, resulting in improved performance and scalability.

To handle non-blocking requests in Spring, you can use the @Async annotation to annotate methods that should be executed asynchronously, and use the TaskExecutor interface to control the execution of asynchronous tasks.

// Annotate a method with @Async
@Service
public class AsyncService {

  @Async
  public Future processRequest() {
    // Perform time-consuming task
    return new AsyncResult<>("Task completed successfully");
  }
}

// Configure a TaskExecutor in Spring configuration class
@Configuration
@EnableAsync
public class AsyncConfig {

  @Bean
  public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("AsyncProcess-");
    executor.initialize();
    return executor;
  }
}

// Call the annotated method from controller
@RestController
public class RequestController {

  @Autowired
  private AsyncService asyncService;

  @GetMapping("/process")
  public String processRequest() throws InterruptedException, ExecutionException {
    Future result = asyncService.processRequest();
    while (!result.isDone());
    return result.get();
  }
}

Or you can also use the Reactor (Mono, Flux) and Spring WebFlux.

Spring WebFlux

Spring WebFlux (introduced in Spring 5) is a non-blocking, reactive web framework built on top of the Spring framework.

In contrast to the blocking model of the Spring MVC framework, Spring WebFlux is designed to handle large numbers of requests concurrently, with low latency and high throughput. It provides Reactive Programming model, which enables the framework to handle requests as streams of events, rather than as individual requests.

Spring WebFlux supports two programming models:

  • Annotation-based: similar to the traditional Spring MVC framework, this model uses annotations to map requests to handler methods.
  • For example:

    @RestController
    public class WebFluxController {
    
      @Autowired
      private ReactiveRepository repository;
    
      @GetMapping("/mono")
      public Mono<String> getMonoResult() {
        return Mono.just("Result from Mono");
      }
    
      @GetMapping("/flux")
      public Flux<String> getFluxResult() {
        return Flux.fromIterable(repository.findAll())
            .map(item -> item + " processed");
      }
    }
    
  • Functional: functional programming concepts, such as lambda expressions and functional interfaces, to define the routing and handling of requests.
  • For example:

    @Configuration
    public class RouterConfig {
    
      @Bean
      public RouterFunction<ServerResponse> routes(GreetingHandler greetingHandler) {
        return RouterFunctions
            .route(RequestPredicates.GET("/mono"), greetingHandler::greeting);
      }
    }
    
    @Component
    public class GreetingHandler {
    
      public Mono greeting(ServerRequest request) {
        return ServerResponse.ok().body(Mono.just("Result from Mono"), String.class);
      }
    }
    

Spring WebFlux provides several features for building reactive web applications, including a router function for handling requests, support for server-sent events and WebSockets, and an extensive set of reactive libraries for handling data and processing events.

Spring Boot WebFlux Rest API example

We will build a Spring Boot CRUD Rest Api using Spring WebFlux for a Tutorial application in that:

  • Each Tutorial has id, title, description, published status.
  • Apis help to create, retrieve, update, delete Tutorials.
  • Apis also support custom finder methods such as find by published status or by title.

These are APIs that we need to provide:

Methods Urls Actions
POST /api/tutorials create new Tutorial
GET /api/tutorials retrieve all Tutorials
GET /api/tutorials/:id retrieve a Tutorial by :id
PUT /api/tutorials/:id update a Tutorial by :id
DELETE /api/tutorials/:id delete a Tutorial by :id
DELETE /api/tutorials delete all Tutorials
GET /api/tutorials/published find all published Tutorials
GET /api/tutorials?title=[keyword] find all Tutorials which title contains keyword

Technology

  • Java 11/17
  • Spring Boot 3 (with Spring WebFlux, Spring Data R2DBC)
  • H2 (embedded database)
  • Maven 3.6.1

Project Structure

spring-boot-webflux-example-rest-api-project

Let me explain it briefly.

Tutorial data model class.
TutorialRepository is an interface that extends R2dbcRepository to interact with the database. It is autowired in TutorialService.
TutorialService is a service component that uses TutorialRepository and provides CRUD methods and custom finder methods for TutorialController.
TutorialController is a RestController which has request mapping methods for RESTful requests such as: getAllTutorials, createTutorial, updateTutorial, deleteTutorial, findByPublished
– Configuration for Spring Data R2DBC is in application.properties.
schema.sql has SQL statement for initializing database table.
pom.xml contains dependencies for Spring Boot, WebFlux, R2DBC and H2 database.

Create & Setup Spring Boot project

Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.

Then open pom.xml and add these dependencies:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
  </dependency>

  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>

  <dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>

Configure the database

Under src/main/resources folder, open application.properties and write these lines.

#spring.data.r2dbc.repositories.enabled=true
spring.r2dbc.url=r2dbc:h2:file:///./testdb

The connection URL is set to r2dbc:h2:file:///./[database-name], which indicates that you are using a disk-based database.
For in-memory database, use r2dbc:h2:mem:///[database-name] instead.

spring.data.r2dbc.repositories.enabled determines the activation of R2DBC repositories in a Spring Boot application.
By default, R2DBC repository support is enabled in a Spring Boot application. If you want to disable R2DBC repository support, you can set the spring.data.r2dbc.repositories.enabled property to false.

@EnableR2dbcRepositories

@EnableR2dbcRepositories is a Spring annotation that is used to enable R2DBC repositories in a Spring Boot application. It provides a convenient way to create a repository layer in a Spring Boot application that uses R2DBC to interact with a database.

Because R2DBC repository support is enabled in our Spring Boot application by default (spring.data.r2dbc.repositories.enabled=true), so that the @EnableR2dbcRepositories is not necessary.

The @EnableR2dbcRepositories annotation could be added to a configuration class in your application, typically the main class that is annotated with @SpringBootApplication:

import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;

@EnableR2dbcRepositories
@SpringBootApplication
public class SpringBootWebfluxExampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootWebfluxExampleApplication.class, args);
  }
}

Define Data Model

Our Data model is Tutorial with four fields: id, title, description, published.
In model package, we define Tutorial class.

model/Tutorial.java

package com.bezkoder.spring.webflux.model;

import org.springframework.data.annotation.Id;

public class Tutorial {
  
  @Id
  private int id;

  private String title;

  private String description;

  private boolean published;

  public Tutorial() {

  }

  public Tutorial(String title, String description, boolean published) {
    this.title = title;
    this.description = description;
    this.published = published;
  }

  // getters and setters

  @Override
  public String toString() {
    return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
  }
}

Create Repository Interface

Let’s create a repository to interact with Tutorials from the database.
In repository package, create TutorialRepository interface that extends R2dbcRepository (which extends Spring Data Reactive ReactiveCrudRepository).

repository/TutorialRepository.java

package com.bezkoder.spring.webflux.repository;

import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.stereotype.Repository;

import com.bezkoder.spring.webflux.model.Tutorial;

import reactor.core.publisher.Flux;

@Repository
public interface TutorialRepository extends R2dbcRepository<Tutorial, Integer>{
  Flux<Tutorial> findByTitleContaining(String title);
  
  Flux<Tutorial> findByPublished(boolean isPublished);
}

Now we can use ReactiveCrudRepository‘s methods: save(), findById(), findAll(), count(), delete(), deleteById(), deleteAll()… without implementing these methods.

We also define custom finder methods:
findByPublished(): returns all Tutorials with published having value as input published.
findByTitleContaining(): returns all Tutorials which title contains input title.

The implementation is plugged in by Spring Data R2DBC automatically.

Create Data Service

Let’s create a service that uses TutorialRepository to implement CRUD Operations and custom finder methods.
In service package, create TutorialService.

service/TutorialService.java

package com.bezkoder.spring.webflux.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.bezkoder.spring.webflux.model.Tutorial;
import com.bezkoder.spring.webflux.repository.TutorialRepository;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class TutorialService {

  @Autowired
  TutorialRepository tutorialRepository;

  public Flux<Tutorial> findAll() {
    return tutorialRepository.findAll();
  }

  public Flux<Tutorial> findByTitleContaining(String title) {
    return tutorialRepository.findByTitleContaining(title);
  }

  public Mono<Tutorial> findById(int id) {
    return tutorialRepository.findById(id);
  }

  public Mono<Tutorial> save(Tutorial tutorial) {
    return tutorialRepository.save(tutorial);
  }

  public Mono<Tutorial> update(int id, Tutorial tutorial) {
    return tutorialRepository.findById(id).map(Optional::of).defaultIfEmpty(Optional.empty())
        .flatMap(optionalTutorial -> {
          if (optionalTutorial.isPresent()) {
            tutorial.setId(id);
            return tutorialRepository.save(tutorial);
          }

          return Mono.empty();
        });
  }

  public Mono<Void> deleteById(int id) {
    return tutorialRepository.deleteById(id);
  }

  public Mono<Void> deleteAll() {
    return tutorialRepository.deleteAll();
  }

  public Flux<Tutorial> findByPublished(boolean isPublished) {
    return tutorialRepository.findByPublished(isPublished);
  }
}

TutorialService class is annotated with @Service to indicate that it is a service component. Spring will create a singleton bean for this class and manage its lifecycle.
– We use @Autowired to inject TutorialRepository bean to local variable.

Create Spring WebFlux Rest API Controller

Finally, we create a controller that provides APIs for creating, retrieving, updating, deleting and finding Tutorials.

controller/TutorialController.java

package com.bezkoder.spring.webflux.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.bezkoder.spring.webflux.model.Tutorial;
import com.bezkoder.spring.webflux.service.TutorialService;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {
  @Autowired
  TutorialService tutorialService;
  
  @GetMapping("/tutorials")
  @ResponseStatus(HttpStatus.OK)
  public Flux<Tutorial> getAllTutorials(@RequestParam(required = false) String title) {
    if (title == null)
      return tutorialService.findAll();
    else
      return tutorialService.findByTitleContaining(title);
  }

  @GetMapping("/tutorials/{id}")
  @ResponseStatus(HttpStatus.OK)
  public Mono<Tutorial> getTutorialById(@PathVariable("id") int id) {
    return tutorialService.findById(id);
  }

  @PostMapping("/tutorials")
  @ResponseStatus(HttpStatus.CREATED)
  public Mono<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
    return tutorialService.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
  }

  @PutMapping("/tutorials/{id}")
  @ResponseStatus(HttpStatus.OK)
  public Mono<Tutorial> updateTutorial(@PathVariable("id") int id, @RequestBody Tutorial tutorial) {
    return tutorialService.update(id, tutorial);
  }

  @DeleteMapping("/tutorials/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public Mono<Void> deleteTutorial(@PathVariable("id") int id) {
    return tutorialService.deleteById(id);
  }

  @DeleteMapping("/tutorials")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public Mono<Void> deleteAllTutorials() {
    return tutorialService.deleteAll();
  }

  @GetMapping("/tutorials/published")
  @ResponseStatus(HttpStatus.OK)
  public Flux<Tutorial> findByPublished() {
    return tutorialService.findByPublished(true);
  }
}

@CrossOrigin is for configuring allowed origins.
@RestController annotation is used to define a controller and to indicate that the return value of the methods should be be bound to the web response body.
@RequestMapping("/api") declares that all Apis’ url in the controller will start with /api.
– We use @Autowired to inject TutorialService bean to local variable.

Enable WebFlux

We use @EnableWebFlux to enable support for reactive web application using the Spring WebFlux framework.

SpringBootWebfluxExampleApplication.java

package com.bezkoder.spring.webflux;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@EnableWebFlux
@SpringBootApplication
public class SpringBootWebfluxExampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootWebfluxExampleApplication.class, args);
  }

}

Create the Database Schema

In src/main/resources folder, create schema.sql file with following content:

CREATE TABLE IF NOT EXISTS tutorial (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255), description VARCHAR(255), published BOOLEAN, PRIMARY KEY (id));

Spring Data R2DBC ConnectionFactoryInitializer provides a convenient way to configure and initialize a connection factory for a reactive database connection in a Spring application. It will scan schema.sql in the classpath, execute SQL script to initialize the database when the database is connected.

SpringBootWebfluxExampleApplication.java

package com.bezkoder.spring.webflux;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
import org.springframework.web.reactive.config.EnableWebFlux;

import io.r2dbc.spi.ConnectionFactory;

@EnableWebFlux
@SpringBootApplication
public class SpringBootWebfluxExampleApplication {

  @Bean
  ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

    ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
    initializer.setConnectionFactory(connectionFactory);
    initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));

    return initializer;
  }

  public static void main(String[] args) {
    SpringApplication.run(SpringBootWebfluxExampleApplication.class, args);
  }

}

Run & Check

Run Spring Boot application with command: mvn spring-boot:run.

Create some Tutorials:

spring-boot-webflux-example-rest-api-crud-create-tutorial

Retrieve all Tutorials:

spring-boot-webflux-example-rest-api-crud-retrieve-tutorial

Update some Tutorials:

spring-boot-webflux-example-rest-api-crud-update-tutorial

Retrieve a Tutorial by Id:

spring-boot-webflux-example-rest-api-crud-retrieve-one-tutorial

Find all published Tutorials:

spring-boot-webflux-example-rest-api-filter-tutorial

Find all Tutorials which title contains a certain string:

spring-boot-webflux-example-rest-api-search-tutorial

Delete a Tutorial:

spring-boot-webflux-example-rest-api-crud-delete-tutorial

Check all current Tutorials:

spring-boot-webflux-example-rest-api-crud-delete-tutorial-check

Delete all Tutorials and check:

spring-boot-webflux-example-rest-api-crud-tutorial-delete

You can also check this Spring Boot App with Client in one of these posts:

Conclusion

Today we’ve known what is Reactive Programming, overview of Blocking and Non-blocking API call, then we’ve built a Spring Boot WebFlux Rest API example successfully using WebFlux Framework, Spring Data Reactive, R2DBC for CRUD and custom finder methods.

Happy learning! See you again.

Source Code

You can find the complete source code for this tutorial on Github.

Further Reading

Fullstack CRUD App:
Spring Boot Thymeleaf example
Vue + Spring Boot example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
Angular 12 + Spring Boot example
Angular 13 + Spring Boot example
Angular 14 + Spring Boot example
Angular 15 + Spring Boot example
React + Spring Boot example

More Practice:
Spring WebFlux File upload example
Spring Boot R2DBC + MySQL example
Spring Boot R2DBC + PostgreSQL example
Spring Boot Reactive + MongoDB example
Secure Spring Boot with Spring Security & JWT Authentication
Spring Boot Rest XML example – Web service with XML Response
Spring Boot Multipart File upload example
Spring Boot Pagination and Sorting example