Navigation
Recherche
|
Reactive Java with Spring WebFlux and Reactor
mercredi 24 septembre 2025, 11:00 , par InfoWorld
Reactive programming is an important coding style that evolved from the functional programming world. Reactive code is ideal for processing and connecting realtime streaming data. You can use reactive programming to compose streams into pipelines that cleanly model complex logic with non-blocking operations.
Java has excellent reactive support, and one of the most popular reactive Java frameworks is Spring WebFlux. This article is a hands-on introduction to reactive Java programming with Spring WebFlux and its default engine, Reactor. Reactive programming with Spring Reactivity is a powerful idiom for describing and combining functionality like web requests and data access. In general, we use producers and subscribers to describe asynchronous event sources and consumers, along with event handlers to describe how streams are created, processed, and combined. In typical Spring framework style, Spring WebFlux provides an abstraction layer for building reactive web components. That means you can use a couple of different underlying reactive implementations. The default is Reactor, which we’ll use for examples in this article. Reactivity vs. virtual threads Java has undergone a revolution in concurrency over the last few years, in the shape of virtual threads and structured concurrency. While these updates have made threading considerably more powerful, we still need reactive engines like Reactor. Reactor gives us a whole paradigm for describing what happens with event streams, and it does it entirely with non-blocking primitives—essential for high-throughput systems. To begin, we’ll initialize a new application with the Spring command-line tool, which requires that you have Java 17 or 21 installed. There are a few ways to install the Spring CLI, but I like using SDKMan. You can find the instructions for installing SDKMan for your operating system here. Once you have SDKMan installed, you can add the Spring CLI with: $ sdk i springboot. Now the command $ spring --version should work. To start the new application, enter: $ spring init --dependencies=webflux --build=maven --language=java spring-reactive Next, cd into the spring-reactive directory. Spring has created a bare-bones layout for us, including a main class at src/main/java/com/example/spring_reactive/DemoApplication.java. Let’s modify this class to add a reactive endpoint handler: package com.example.springreactive; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @RestController public class EchoController { @GetMapping('/hello') public Mono hello() { return Mono.just('Hello, InfoWorld!'); } @GetMapping('echo/{str}') public Mono echo(@PathVariable String str) { return Mono.just('Echo: ' + str); } @GetMapping('echoquery') public Mono echoQuery(@RequestParam('name') String name) { return Mono.just('Hello, ' + name); } } } The Mono type In the Reactor library, Mono is a type that refers to a “monadic value.” (A monad is a functional container for a single value.) The @SpringBootApplication took care of much of the configuration for us. We use an inner class called EchoController with the @RestController annotation to let Spring know what endpoints we’re using. The above code presents three examples, each mapped to a URL path with @GetMapping: /hello simply writes a greeting to the response. /echo/{str} shows how to take a URL parameter (a path variable) and use it in the response. /echoquery shows how to grab a request parameter (the values in the URL after a question mark) and use it. In each case, we rely on the Mono.just() method to describe the response. This is a simple way to create an event producer in the Reactor framework. It says: make an event producer with a single event, found in the argument, and hand it off to all subscribers. In this case, the subscribers are handled by the Spring WebFlux framework and the nonblocking server hosting it. In short, we get access to an entirely nonblocking pipeline to produce the response. The inbound request processing is also built entirely on nonblocking IO. This makes scaling the server potentially very efficient because there are no blocking threads to limit concurrency. Especially in real-time systems, nonblocking IO can be very important to overall throughput. Spring WebFlux uses the Netty server by default. If you prefer, you can use another server like Undertow or a Servlet 3.1 container like Tomcat. See the WebFlux documentation for more about server options. (You can also peruse these docs to learn about the relationship between Spring WebFlux and standard Spring MVC.) If you want to test this example, just run $mvn spring-boot:run and try the endpoints out at localhost:8080. Also see: Full-stack development with Java, React, and Spring Boot. Reactive Java programming examples Reactive programming entails a whole mindset and set of concepts, which is more than we can explore here. Instead, we’ll work through a few examples that expose the critical aspects of this programming style. Accepting and writing a file To start, let’s make an endpoint that accepts a file upload post and writes the contents to disk. You can see this method, along with its imports, in the following code. The remainder of the code stays the same (make sure you put the new method inside the body of the Controller): import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.util.FileSystemUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.file.Path; @PostMapping(value = '/writefile', consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono writeFile(@RequestPart('file') Flux filePartFlux) { Path path = Path.of('/tmp/file.txt'); // Delete the existing file if it already exists FileSystemUtils.deleteRecursively(path.toFile()); // Save the file parts to the specified path return filePartFlux.flatMap(filePart -> filePart.transferTo(path)).then(Mono.just('File saved: ' + path.toString())); } The writeFile() method is annotated with @PostMapping, and this is configured to accept a multipart form upload. So far, this is a normal Spring Web configuration. WebFlux lets us use the @RequestPart annotation in the method argument with a type of Flux. This lets us accept the multipart chunks in a reactive, nonblocking way with Flux. With the filePartFlux in hand, we can make use of the reactive flatMap method to write it to disk: filePartFlux.flatMap(filePart -> filePart.transferTo(path)). Each “event” of the multipart file is handed to the transferTo function to be added to the file. This is very idiomatic reactive style programming. It is taking two different “flows” of data and connecting them together in a pipeline, describing how the data is transformed using the connecting operator. The use of higher-order functions like flatMap to transform and process event streams is good reactive programming. As in our example, a good reactive program involves the use of event producers (the incoming multipart stream), subscribers (in our case implicitly handled by the Spring endpoint), and transformers like flatMap. By taking one or many streams and manipulating them with chains of metafunctions, you can achieve powerful effects with relatively simple syntax. Testing the writeFile endpoint To test the new endpoint, you can use a curl command, as shown below. (You will need to create /tmp/file.txt first to accept the incoming date.) $ echo 'I regard consciousness as fundamental. I regard matter as derivative from consciousness. We cannot get behind consciousness.' >> testfile.txt $ curl -X POST -F 'file=@./testfile.txt' File saved: tmpfile.txt $ cat /tmp/file.txt I regard consciousness as fundamental. I regard matter as derivative from consciousness. We cannot get behind consciousness. In this example, we create a testfile.txt file with some content (“I regard consciousness as fundamental. I regard matter as derivative from consciousness. We cannot get behind consciousness.”), then send it to the endpoint as a POST with multipart form data (-F), receive the response, and verify the new file contents. Although this is a simple example, all the elements are in place for handling high-volume streams of data with excellent scalability, in a format that is very clear and easy to understand. Using the reactive HTTP client Now, let’s make an endpoint that accepts an ID parameter. We’ll use the Spring reactive HTTP client and An API of Ice and Fire to make a request for a Game of Thrones character based on ID. Then, we’ll send the character data back to the user. Notice the new apiChain() method and its imports here: import org.springframework.web.reactive.function.client.WebClient; @GetMapping('character/{id}') public Mono getCharacterData(@PathVariable String id) { WebClient client = WebClient.create('https://anapioficeandfire.com/api/characters/'); return client.get().uri('/{id}', id).retrieve().bodyToMono(String.class).map(response -> 'Character data: ' + response); } If you navigate to localhost:8080/character/148, you’ll get the biographical information for who is obviously the best character in The Game of Thrones. This example works by accepting the ID path parameter and using it to make a request to the WebClient class. In this case, we are creating an instance for our request, but you can create a WebClient with a base URL and then reuse it repeatedly with many paths. We put the ID into the path and then call retrieve followed by bodyToMono(), which transforms the response into a Mono. Remember that all this remains nonblocking and asynchronous, so the code that waits for the response from the API will not block the thread. Finally, we use map() to formulate a response back to the user. Again, this is the essence of reactive programming: tying together input and output streams asynchronously. The overall effect, as demonstrated by these examples, is to enable a high-performance stack from the server all the way to your code, with minimal fuss. Want more reactive Java? See Reactive programming with RxJava. Conclusion Reactive programming is a different way of thinking from the more familiar imperative style. While it might be unfamiliar, and challenging to understand at first, reactive programming is an extremely powerful way to orchestrate data flows descriptively. It is possible to use Spring WebFlux with Reactor alongside standard blocking-style Spring MVC, either by calling from blocking code to reactive code or vice versa. See the Spring WebFlux documentation for a discussion of how to combine the two frameworks.
https://www.infoworld.com/article/2338760/reactive-java-with-spring-webflux-and-reactor.html
Voir aussi |
56 sources (32 en français)
Date Actuelle
jeu. 25 sept. - 19:05 CEST
|