arrow left
Back to Developer Education

Getting Started with Spring Boot Webflux

Getting Started with Spring Boot Webflux

Reactive programming supports an asynchronous, event-driven, and non-blocking approach to data processing. It organizes events and data as streams. <!--more--> In reactive programming paradigm, when a request is made, other tasks are executed while waiting for the results.

When the data is available, a notification is sent with the results through the callback function.

The reactive programming paradigm is, therefore, suitable for data-driven applications such as chat apps.

In this tutorial, we are going to create a student management system using Spring Webflux and MongoDB.

Prerequisites

  • JDK installed on your computer.
  • Your favorite IDE or editor installed.
  • Knowledge of Java and Spring Boot.
  • Knowledge of MongoDB.

Streams API

Software developers at Netflix, Twitter, Pivotal, as well as Redhat collaborated and created the streams API. Streams API defines four interfaces, discussed below.

Publisher

The publisher interface emits events to subscribers based on the request sent. Thus, a single publisher can serve several subscribers.

public interface Publisher<T> 
{
    public void subscribe(Subscriber<? super T> s);
}

Subscriber

The Subscriber interface listens and receives events from the Publisher interface. The Subscriber interface has four methods to handle the response from the Publisher interface.

public interface Subscriber<T> 
{
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

Subscription

The Subscription interface defines a one-to-one relationship between the Publisher and Subscriber interfaces. It can be used to request data and also cancel the request.

public interface Subscription<T> 
{
    public void request(long n);
    public void cancel();
}

Processor

The Processor interface represents the processing stage containing both the Publisher and the Subscriber.

public interface Processor<T, R> extends Subscriber<T>, Publisher<R>{

}

The two popular implementations of reactive streams are RxJava and Project reactor.

Spring Webflux

Spring Webflux is similar to Spring MVC, but it supports reactive and non-blocking streams.

Spring Webflux has two publishers:

Mono

Mono is a publisher that returns 0 or 1 element.

Mono<String> mono = Mono.just("Jonh");
Mono<String> mono = Mono.empty();

Flux

Flux is a publisher that emits 0 or N elements.

Flux<String> flux = Flux.just("x", "y", "z");
Flux<String> flux = Flux.fromArray(new String[]{"x", "y", "z"});
Flux<String> flux = Flux.fromIterable(Arrays.asList("x", "y", "z"));
 
// To subscribe, call the method
 
flux.subscribe();

Application Setup

We are going to use spring initializr to generate our application startup code.

  1. On your web browser, navigate to spring initializr.

  2. Input the group as io.section and name as webfluxexample.

  3. Add Spring webflux, Mongo reactive, and Lombok as project dependencies.

  4. Click generate to download the startup project files as a zip.

  5. Extract the zip file and open the project in your favorite code editor or IDE.

  6. Add the following dependencies below to the pom.xml file.

    <dependencies>
      <dependency>
         <groupId>javax.xml.bind</groupId>
         <artifactId>jaxb-api</artifactId>
         <version>2.3.0</version>
      </dependency>
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>javax.servlet-api</artifactId>
         <version>3.1.0</version>
         <scope>provided</scope>
      </dependency>
    </dependencies>
    

Configuration layer

Create a new file named MongoConfig.java in the config package that we created earlier and then add the code snippet below.

@Configuration
@EnableMongoRepositories(basePackages = "io.section.webfluxexample.repositories")
public class MongoDBConfig extends AbstractReactiveMongoConfiguration {

    @Value("${database.name}")
    private String databaseName;

    @Value("${database.host}")
    private String databaseHost;


    @Override
    protected String getDatabaseName() {
        return databaseName;
    }

    @Override
    public MongoClient reactiveMongoClient() {
        String name = databaseHost;
        return MongoClients.create(name);
    }

    @Bean
    public ReactiveMongoTemplate reactiveMongoTemplate() {
        return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
    }
}

For instructions on creating MongoDB collection in Mongo atlas, read Spring Data and MongoDB.

In the config package, create a new file named WebFluxConfig.java and add the code snippet below.

@Configuration // Marks the class as configuration class
@EnableWebFlux // Enables Webflux in our application
public class WebFluxConfig implements WebFluxConfigurer {
}

In the resources directory, add the code snippet below in the applications.properties file.

database.name=myFirstDatabase # database name property
database.host = mongodb+srv://<username>:<password>@cluster0.mk0n7.gcp.mongodb.net/myFirstDatabase?retryWrites=true&w=majority #database connection string from mongo atlas

Data layer

In the root project package, create a new package named model.

In the model package created above, create a new file named Student.java and add the code snippets below.

@Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Document // Marks this class as a MongoDB document
@Data // Lombok annotation to generate getters, setters, toString, and equals methods
public class Student {
    @Id
    private int id;
    private String name;
    private String course;
}

Repository layer

In the root project directory, create a new package named repository.

Create a new file named StudentRepository.java in the generated repository package.

public interface StudentRepository extends ReactiveMongoRepository<Student, Integer> {
    @Query("{ 'name': ?0 }")
    Flux<Student> findByName(final String name); // Flux returns zero or n elements
}

Service layer

In the root project package, create a new package named service.

Create a file named StudentService.java and add the code snippet below.

public interface StudentService {
    void createStudent(Student student); // Returns null after creating a student

    Mono<Student> findById(int id); // Returns 0 or a single student

    Flux<Student> findByName(String name); // Returns a list of students whose names match the searched name

    Flux<Student> findAll(); // Returns all students

    Mono<Student> update(Student student, int id); // Updates and returns the updated student

    Mono<Void> delete(int id); // Delete the student

}

In the service package, create a new file named StudentServiceImpl.java and add the code snippet below.

@Service
@AllArgsConstructor
public class StudentServiceImpl implements StudentService {
    private final StudentRepository repository;
    // Saves the student into the database
    @Override
    public void createStudent(Student student) {

        repository.insert(student).subscribe();
    }
    // Finds a single student by id
    @Override
    public Mono<Student> findById(int id) {
        return repository.findById(id);
    }
    // Finds a list of students whose names match the searched name
    @Override
    public Flux<Student> findByName(String name) {
        return repository.findByName(name);
    }
    // Returns a list of all students from the database
    @Override
    public Flux<Student> findAll() {
        return repository.findAll();
    }
    // Saves a student into the database
    @Override
    public Mono<Student> update(Student student, int id) {
        return repository.findById(id) // tries to get a student with the specified id
                .doOnError(IllegalStateException::new) 
                .map(studentMap -> {
                    studentMap.setName(student.getName());
                    studentMap.setCourse(student.getCourse());
                    return studentMap;
                }).flatMap(repository::save); // Updates the student with the id passed if the student is present in the database
    }
    // Deletes a student from the database
    @Override
    public Mono<Void> delete(int id) {
        return repository.deleteById(id);
    }
}

Controller layer

In the root project package, create a new package named controllers.

Create a new file named StudentController.java in the controllers package created above.

@RestController // Marks this class as a REST controller
@RequestMapping("/api/students") // Sets the base URL for students API
@AllArgsConstructor // Lombok annotation to generates a constructor for the class
public class StudentController {
    private final StudentService service;
    // Handles the student creation POST request.
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void createStudent(Student student) {
        service.createStudent(student);
    }
    // Handles get student by id endpoint
    @GetMapping("/id/{id}")
    public ResponseEntity<Mono<Student>> getById(@PathVariable("id") int id) {
        Mono<Student> student = service.findById(id);
        HttpStatus status = student != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
        return new ResponseEntity<>(student, status);
    }
    // Handles the search student by name endpoint
    @GetMapping("/name/{name}")
    public Flux<Student> getByName(@PathVariable("name") String name) {
        return service.findByName(name);
    }
    // Returns a list of students
    @GetMapping
    public Flux<Student> findAll() {
        return service.findAll();
    }
    // Updates the student with the provided id
    @PatchMapping("/{id}")
    public Mono<Student> updateStudent(@RequestBody Student student, @PathVariable("id") int id) {
        return service.update(student, id);
    }
    // Deletes the student with the provided id
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteStudent(@PathVariable("id") int id) {
        service.delete(id).subscribe();
    }
}

We need to populate the database with some dummy data whenever our application starts.

In the application class, add the code snippet below.

@SpringBootApplication
public class WebFluxExampleApplication {
    //This block of code executes everytime the application starts
    @Bean
    CommandLineRunner employees(StudentRepository studentRepository) {
        return args -> studentRepository
                .deleteAll() // deletes all the records in the database
                .subscribe(null, null, () -> Stream.of(
                                new Student(1, "Samuel", "Computer science"),
                                new Student(2, "Dana", "Electrical engineering"),
                                new Student(3, "Paul", "Pure and Applied mathematics"),
                                new Student(4, "Denis", "Software engineering")
                        )
                        .forEach(student -> {
                            studentRepository
                                    .save(student) // saves all the new records in the database
                                    .subscribe(System.out::println);

                        }));

    }


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

}

Testing

We will test the application using Postman.

Adding a student

Make a POST request to http://localhost:8080/api/students on Postman, and pass the following JSON payload in the request body.

{
    "name": "Job", //Student name
    "course": "Software engineering" //student
}

Getting all students

Make a GET request to http://localhost:8080/api/students on Postman.

Get all students

Getting a student by id

Make a GET request to http://localhost:8080/api/students/id/2 on Postman. Number 2 at the end of the URL is the student's id.

Get student by id

Getting student by name

Make a GET request to http://localhost:8080/api/students/name/Denison Postman. Denis is the name of the student whose details will be returned.

Getting student by name

Updating student details

Make a PUT request to http://localhost:8080/api/students/2 on Postman, passing in the JSON payload below in the request body.

{
    "id": 2,
    "name": "Diana",
    "course": "Electrical engineering"
}

Updating student details

Deleting a student

Make a DELETE request to http://localhost:8080/api/students/2 on postman. The number 2 at the end of the URL is the id of the student to be deleted.

Deleting student

Conclusion

With the knowledge you have gained from reading this article, try implementing a chat system using Spring Boot Webflux with any frontend client of your choice.

You can download the complete source code here.


Peer Review Contributions by: John Amiscaray

Published on: Sep 20, 2021
Updated on: Jul 12, 2024
CTA

Start your journey with Cloudzilla

With Cloudzilla, apps freely roam across a global cloud with unbeatable simplicity and cost efficiency