DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Cloud Stream: A Brief Guide
  • Frequently Used Annotations in Spring Boot Applications
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Trending

  • The Role of Retrieval Augmented Generation (RAG) in Development of AI-Infused Enterprise Applications
  • Ensuring Configuration Consistency Across Global Data Centers
  • Grafana Loki Fundamentals and Architecture
  • Java's Quiet Revolution: Thriving in the Serverless Kubernetes Era
  1. DZone
  2. Coding
  3. Frameworks
  4. WebFlux: Reactive Programming With Spring, Part 3

WebFlux: Reactive Programming With Spring, Part 3

The third part of the blog series contains an introduction to WebFlux – Spring's reactive web framework.

By 
Anna Eriksson user avatar
Anna Eriksson
·
Apr. 08, 21 · Tutorial
Likes (18)
Comment
Save
Tweet
Share
32.7K Views

Join the DZone community and get the full member experience.

Join For Free

This is the third part of my blog series on reactive programming, which will give an introduction to WebFlux — Spring’s reactive web framework.

1. An Introduction to Spring Webflux

The original web framework for Spring — Spring Web MVC — was built for the Servlet API and Servlet containers.

WebFlux was introduced as part of Spring Framework 5.0. Unlike Spring MVC, it does not require the Servlet API. It is fully asynchronous and non-blocking, implementing the Reactive Streams specification through the Reactor project (see my previous blog post).

WebFlux requires Reactor as a core dependency but it is also interoperable with other reactive libraries via Reactive Streams.

1.1 Programming Models

Spring WebFlux supports two different programming models: annotation-based and functional.

1.1.1 Annotated Controllers

If you have worked with Spring MVC, the annotation-based model will look quite familiar since it is using the same annotations from the Spring Web module as are being used with Spring MVC. The major difference being that the methods now return the reactive types Mono and Flux. See the following example of a RestController using the annotation-based model:

Java
 




x
46


 
1
@RestController
2
@RequestMapping("/students")
3
public class StudentController {
4

          
5
    @Autowired
6
    private StudentService studentService;
7

          
8

          
9
    public StudentController() {
10
    }
11

          
12
    @GetMapping("/{id}")
13
    public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) {
14
        return studentService.findStudentById(id)
15
                .map(ResponseEntity::ok)
16
                .defaultIfEmpty(ResponseEntity.notFound().build());
17
    }
18

          
19
    @GetMapping
20
    public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) {
21
        return studentService.findStudentsByName(name);
22
    }
23

          
24
    @PostMapping
25
    public Mono<Student> addNewStudent(@RequestBody Student student) {
26
        return studentService.addNewStudent(student);
27
    }
28

          
29
    @PutMapping("/{id}")
30
    public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) {
31
        return studentService.updateStudent(id, student)
32
                .map(ResponseEntity::ok)
33
                .defaultIfEmpty(ResponseEntity.notFound().build());
34
    }
35

          
36
    @DeleteMapping("/{id}")
37
    public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {
38
        return studentService.findStudentById(id)
39
                .flatMap(s ->
40
                        studentService.deleteStudent(s)
41
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
42
                )
43
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
44
    }
45
}



Some explanations about the functions used in the example:

  • The map function is used to transform the item emitted by a Mono by applying a synchronous function to it.
  • The flatMap function is used to transform the item emitted by the Mono asynchronously, returning the value emitted by another Mono.
  • The defaultIfEmpty function provides a default value if a Mono is completed without any data.

1.1.2 Functional Endpoints

The functional programming model is lambda-based and leaves the application in charge of the full request handling. It is based on the concepts of HandlerFunctions and RouterFunctions.

HandlerFunctions are used to generate a response for a given request:

Java
 




xxxxxxxxxx
1


 
1
@FunctionalInterface
2
public interface HandlerFunction<T extends ServerResponse> {
3
    Mono<T> handle(ServerRequest request);
4
}



The RouterFunction is used to route the requests to the HandlerFunctions:

Java
 




xxxxxxxxxx
1


 
1
@FunctionalInterface
2
public interface RouterFunction<T extends ServerResponse> {
3
    Mono<HandlerFunction<T>> route(ServerRequest request);
4
    ...
5
}



Continuing with the same student example we would get something like the following using the functional style.

A StudentRouter:

Java
 




xxxxxxxxxx
1
23


 
1
@Configuration
2
public class StudentRouter {
3

          
4
    @Bean
5
    public RouterFunction<ServerResponse> route(StudentHandler studentHandler){
6
        return RouterFunctions
7
            .route(
8
                GET("/students/{id:[0-9]+}")
9
                    .and(accept(APPLICATION_JSON)), studentHandler::getStudent)
10
            .andRoute(
11
                GET("/students")
12
                    .and(accept(APPLICATION_JSON)), studentHandler::listStudents)
13
            .andRoute(
14
                POST("/students")
15
                    .and(accept(APPLICATION_JSON)),studentHandler::addNewStudent)
16
            .andRoute(
17
                PUT("students/{id:[0-9]+}")
18
                    .and(accept(APPLICATION_JSON)), studentHandler::updateStudent)
19
            .andRoute(
20
                DELETE("/students/{id:[0-9]+}")
21
                    .and(accept(APPLICATION_JSON)), studentHandler::deleteStudent);
22
    }
23
}



And a StudentHandler:

Java
 




xxxxxxxxxx
1
51


 
1
@Component
2
public class StudentHandler {
3

          
4
    private StudentService studentService;
5

          
6
    public StudentHandler(StudentService studentService) {
7
        this.studentService = studentService;
8
    }
9

          
10
    public Mono<ServerResponse> getStudent(ServerRequest serverRequest) {
11
        Mono<Student> studentMono = studentService.findStudentById(
12
                Long.parseLong(serverRequest.pathVariable("id")));
13
        return studentMono.flatMap(student -> ServerResponse.ok()
14
                .body(fromValue(student)))
15
                .switchIfEmpty(ServerResponse.notFound().build());
16
    }
17

          
18
    public Mono<ServerResponse> listStudents(ServerRequest serverRequest) {
19
        String name = serverRequest.queryParam("name").orElse(null);
20
        return ServerResponse.ok()
21
                .contentType(MediaType.APPLICATION_JSON)
22
                .body(studentService.findStudentsByName(name), Student.class);
23
    }
24

          
25
    public Mono<ServerResponse> addNewStudent(ServerRequest serverRequest) {
26
        Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);
27
        return studentMono.flatMap(student ->
28
                ServerResponse.status(HttpStatus.OK)
29
                        .contentType(MediaType.APPLICATION_JSON)
30
                        .body(studentService.addNewStudent(student), Student.class));
31

          
32
    }
33

          
34
    public Mono<ServerResponse> updateStudent(ServerRequest serverRequest) {
35
        final long studentId = Long.parseLong(serverRequest.pathVariable("id"));
36
        Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);
37

          
38
        return studentMono.flatMap(student ->
39
                ServerResponse.status(HttpStatus.OK)
40
                        .contentType(MediaType.APPLICATION_JSON)
41
                        .body(studentService.updateStudent(studentId, student), Student.class));
42
    }
43

          
44
    public Mono<ServerResponse> deleteStudent(ServerRequest serverRequest) {
45
        final long studentId = Long.parseLong(serverRequest.pathVariable("id"));
46
        return studentService
47
                .findStudentById(studentId)
48
                .flatMap(s -> ServerResponse.noContent().build(studentService.deleteStudent(s)))
49
                .switchIfEmpty(ServerResponse.notFound().build());
50
    }
51
}



Some explanations about the functions used in the example:

  • The switchIfEmpty function has the same purpose as defaultIfEmpty, but instead of providing a default value, it is used for providing an alternative Mono.

Comparing the two models we can see that:

  • Using the functional variant requires some more code for things such as retrieving input parameters and parsing to the expected type.
  • Not relying on annotations, but writing explicit code does offer some more flexibility and could be a better choice if we for example need to implement more complex routing.

1.2 Server Support

WebFlux runs on non-Servlet runtimes such as Netty and Undertow (non-blocking mode) as well as Servlet 3.1+ runtimes such as Tomcat and Jetty.

The Spring Boot WebFlux starter defaults to using Netty, but it is easy to switch by changing your Maven or Gradle dependencies.

For example, to switch to Tomcat, just exclude spring-boot-starter-netty from the spring-boot-starter-webflux dependency and add spring-boot-starter-tomcat:

XML
 




xxxxxxxxxx
1
15


 
1
<dependency>
2
    <groupId>org.springframework.boot</groupId>
3
    <artifactId>spring-boot-starter-webflux</artifactId>
4
    <exclusions>
5
        <exclusion>
6
            <groupId>org.springframework.boot</groupId>
7
            <artifactId>spring-boot-starter-netty</artifactId>
8
        </exclusion>
9
    </exclusions>
10
</dependency>
11

          
12
<dependency>
13
    <groupId>org.springframework.boot</groupId>
14
    <artifactId>spring-boot-starter-tomcat</artifactId>
15
</dependency>



1.3 Configuration

Spring Boot provides auto-configuration for Spring WebFlux that works well for common cases. If you want full control of the WebFlux configuration, the @EnableWebFlux annotation can be used (this annotation would also be needed in a plain Spring application to import the Spring WebFlux configuration).

If you want to keep the Spring Boot WebFlux config and just add some additional WebFlux configuration, you can add your own @Configuration class of type WebFluxConfigurer (but without @EnableWebFlux).

For details and examples, read the WebFlux config documentation.

2. Securing Your Endpoints

To get Spring Security WebFlux support, first, add the spring-boot-starter-security dependency to your project. Now you can enable it by adding the @EnableWebFluxSecurity annotation to your Configuration class (available since Spring Security 5.0).

The following simplified example would add support for two users, one with a USER role and one with an ADMIN role, enforce HTTP basic authentication, and require the ADMIN role for any access to the path /students/admin:

Java
 




xxxxxxxxxx
1
38


 
1
@EnableWebFluxSecurity
2
public class SecurityConfig {
3

          
4
    @Bean
5
    public MapReactiveUserDetailsService userDetailsService() {
6

          
7
        UserDetails user = User
8
                .withUsername("user")
9
                .password(passwordEncoder().encode("userpwd"))
10
                .roles("USER")
11
                .build();
12

          
13
        UserDetails admin = User
14
                .withUsername("admin")
15
                .password(passwordEncoder().encode("adminpwd"))
16
                .roles("ADMIN")
17
                .build();
18

          
19
        return new MapReactiveUserDetailsService(user, admin);
20
    }
21

          
22
    @Bean
23
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
24
        return http.authorizeExchange()
25
                .pathMatchers("/students/admin")
26
                .hasAuthority("ROLE_ADMIN")
27
                .anyExchange()
28
                .authenticated()
29
                .and().httpBasic()
30
                .and().build();
31
    }
32

          
33
    @Bean
34
    public PasswordEncoder passwordEncoder() {
35
        return new BCryptPasswordEncoder();
36
    }
37

          
38
}



It is also possible to secure a method rather than a path, by first adding the annotation @EnableReactiveMethodSecurity to your config:

Java
 




x


 
1
@EnableWebFluxSecurity
2
@EnableReactiveMethodSecurity
3
public class SecurityConfig {
4
    ...
5
}



And then adding the @PreAuthorize annotation to the methods to be secured. We might for example want our POST, PUT and DELETE methods only to be accessible by the ADMIN role. Then the PreAuthorize annotation could be applied to those methods, like:

Java
 




xxxxxxxxxx
1


 
1
@DeleteMapping("/{id}")
2
@PreAuthorize("hasRole('ADMIN')")
3
public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {
4
    ...
5
}



Spring Security offers more support related to WebFlux applications, such as CSRF protection, OAuth2 integration, and reactive X.509 authentication. For more details, read the following section in the Spring Security documentation: Reactive Applications.

3. WebClient

Spring WebFlux also includes a reactive, fully non-blocking web client. It has a functional, fluent API based on Reactor.

Let’s take a look at a (once again) simplified example, how the WebClient can be used to query our StudentController:

Java
 




xxxxxxxxxx
1
59


 
1
public class StudentWebClient {
2

          
3
    WebClient client = WebClient.create("http://localhost:8080");
4

          
5
        public Mono<Student> get(long id) {
6
            return client
7
                    .get()
8
                    .uri("/students/" + id)
9
                    .headers(headers -> headers.setBasicAuth("user", "userpwd"))
10
                    .retrieve()
11
                    .bodyToMono(Student.class);
12
        }
13
    
14
        public Flux<Student> getAll() {
15
            return client.get()
16
                    .uri("/students")
17
                    .headers(headers -> headers.setBasicAuth("user", "userpwd"))
18
                    .retrieve()
19
                    .bodyToFlux(Student.class);
20
        }
21
    
22
        public Flux<Student> findByName(String name) {
23
            return client.get()
24
                    .uri(uriBuilder -> uriBuilder.path("/students")
25
                    .queryParam("name", name)
26
                    .build())
27
                    .headers(headers -> headers.setBasicAuth("user", "userpwd"))
28
                    .retrieve()
29
                    .bodyToFlux(Student.class);
30
        }
31
    
32
        public Mono<Student> create(Student s)  {
33
            return client.post()
34
                    .uri("/students")
35
                    .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
36
                    .body(Mono.just(s), Student.class)
37
                    .retrieve()
38
                    .bodyToMono(Student.class);
39
        }
40
    
41
        public Mono<Student> update(Student student)  {
42
            return client
43
                    .put()
44
                    .uri("/students/" + student.getId())
45
                    .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
46
                    .body(Mono.just(student), Student.class)
47
                    .retrieve()
48
                    .bodyToMono(Student.class);
49
        }
50
    
51
        public Mono<Void> delete(long id) {
52
            return client
53
                    .delete()
54
                    .uri("/students/" + id)
55
                    .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
56
                    .retrieve()
57
                    .bodyToMono(Void.class);
58
        }
59
}



4. Testing

For testing your reactive web application, WebFlux offers the WebTestClient, which comes with a similar API as the WebClient.

Let’s have a look at how we can test our StudentController using the WebTestClient:

Java
 




xxxxxxxxxx
1
40


 
1
@ExtendWith(SpringExtension.class)
2
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
3
class StudentControllerTest {
4
    @Autowired
5
    WebTestClient webClient;
6

          
7
    @Test
8
    @WithMockUser(roles = "USER")
9
    void test_getStudents() {
10
        webClient.get().uri("/students")
11
                .header(HttpHeaders.ACCEPT, "application/json")
12
                .exchange()
13
                .expectStatus().isOk()
14
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
15
                .expectBodyList(Student.class);
16

          
17
    }
18

          
19
    @Test
20
    @WithMockUser(roles = "ADMIN")
21
    void testAddNewStudent() {
22
        Student newStudent = new Student();
23
        newStudent.setName("some name");
24
        newStudent.setAddress("an address");
25

          
26
        webClient.post().uri("/students")
27
                .contentType(MediaType.APPLICATION_JSON)
28
                .accept(MediaType.APPLICATION_JSON)
29
                .body(Mono.just(newStudent), Student.class)
30
                .exchange()
31
                .expectStatus().isOk()
32
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
33
                .expectBody()
34
                .jsonPath("$.id").isNotEmpty()
35
                .jsonPath("$.name").isEqualTo(newStudent.getName())
36
                .jsonPath("$.address").isEqualTo(newStudent.getAddress());
37
    }
38

          
39
    ...
40
}



5. WebSockets and RSocket

5.1 WebSockets

With Spring 5, WebSockets also gets added reactive capabilities. To create a WebSocket server, you can create an implementation of the WebSocketHandler interface, which holds the following method:

Java
 




xxxxxxxxxx
1


 
1
Mono<Void> handle(WebSocketSession session)



This method is invoked when a new WebSocket connection is established and allows handling of the session. It take a WebSocketSession as input and returns Mono<Void> to signal when application handling of the session is complete.

The WebSocketSession has methods defined for handling the inbound and outbound streams:

Java
 




xxxxxxxxxx
1


 
1
Flux<WebSocketMessage> receive()
2
Mono<Void> send(Publisher<WebSocketMessage> messages)



Spring WebFlux also provides a WebSocketClient with implementations for Reactor Netty, Tomcat, Jetty, Undertow, and standard Java.

For more details, read the following chapter in Spring’s Web on Reactive Stack documentation: WebSockets

5.2 RSocket

RSocket is a protocol that models Reactive Streams semantics over a network. It is a binary protocol for use on byte stream transports such as TCP, WebSockets, and Aeron. For an introduction to this topic, I recommend the following blog post that my colleague Pär has written: An introduction to RSocket.

And for more details on Spring Framework’s support for the RSocket protocol: RSocket.

6. To Summarize…

This blog post demonstrated how WebFlux can be used to build a reactive web application. The next and final blog post in this series will show how we can make our entire application stack fully non-blocking by also implementing non-blocking database communication — using R2DBC (Reactive Relational Database Connectivity)!

References

Spring Framework documentation — Web on Reactive Stack

Spring Boot Features — The Spring WebFlux framework

Spring Security — Reactive Applications

Spring Framework Spring Security Reactive programming application Java (programming language) Spring Boot Annotation

Published at DZone with permission of Anna Eriksson. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Spring Cloud Stream: A Brief Guide
  • Frequently Used Annotations in Spring Boot Applications
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!