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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

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

Related

  • How To Build Web Service Using Spring Boot 2.x
  • How To Build Self-Hosted RSS Feed Reader Using Spring Boot and Redis
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

Trending

  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Testing SingleStore's MCP Server
  • Debugging With Confidence in the Age of Observability-First Systems
  • How to Format Articles for DZone
  1. DZone
  2. Coding
  3. Frameworks
  4. Doing Stuff With Spring WebFlux

Doing Stuff With Spring WebFlux

Interested in Spring WebFlux's reactive capabilities but aren't sure where to get started? Here's a deep dive into routes, handlers, and requests.

By 
Dan Newton user avatar
Dan Newton
·
Mar. 18, 18 · Tutorial
Likes (17)
Comment
Save
Tweet
Share
89.9K Views

Join the DZone community and get the full member experience.

Join For Free

Spring Boot 2.0 went GA recently, so I decided to write my first post about Spring in quite a while. Since the release, I have been seeing more and more mentions of Spring WebFlux along with tutorials on how to use it. But after reading through them and trying to get it working myself, I found it a bit hard to make the jump from the code included in the posts and tutorials I read to writing code that actually does something a tiny bit more interesting than returning a string from the back-end. Now, I’m hoping I’m not shooting myself in the foot by saying that, as you could probably make the same criticism of the code I use in this post, but here is my attempt to give a tutorial of Spring WebFlux that actually resembles something that you might use in the wild.

Before I continue, and after all this mentioning of WebFlux, what actually is it? Spring WebFlux is a fully non-blocking reactive alternative to Spring MVC. It allows better vertical scaling without increasing your hardware resources. Being reactive, it now makes use of Reactive Streams to allow asynchronous processing of data returned from calls to the server. This means we are going to see a lot less Lists, Collections, or even single objects. Instead, we'll see their reactive equivalents such as Flux and Mono (from Reactor). I’m not going to go in depth on what Reactive Streams are, as honestly, I need to look into it even more myself before I try to explain it to anyone. Instead, let's get back to focusing on WebFlux.

I used Spring Boot to write the code in this tutorial as usual.

Below are the dependencies that I used in this post.

<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-cassandra-reactive</artifactId>
        <version>2.0.0.RELEASE</version>
    </dependency>
</dependencies>


Although I didn’t include it in the dependency snippet above, the spring-boot-starter-parent is used, which can finally be upped to version 2.0.0.RELEASE. Being that this tutorial is about WebFlux, including the spring-boot-starter-webflux is obviously a good idea. spring-boot-starter-data-cassandra-reactive has also been included, as we will be using this as the database for the example application — it is one of the few databases that have reactive support (at the time of writing). By using these dependencies together, our application can be fully reactive from front to back.

WebFlux introduces a different way to handle requests instead of using the @Controller or @RestController programming model that is used in Spring MVC. At the same time, it does not replace it. Instead, it has been updated to allow reactive types to be used. This allows you to keep the same format that you are used to writing with Spring but with a few changes to the return types so Fluxs or Monos are returned instead. Below is a very contrived example.

@RestController
public class PersonController {

    private final PersonRepository personRepository;

    public PersonController(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @GetMapping("/people")
    public Flux<Person> all() {
        return personRepository.findAll();
    }

    @GetMapping("/people/{id}")
	    Mono<Person> findById(@PathVariable String id) {
		return personRepository.findOne(id);
	}
}


To me, this looks very familiar, and from a quick glance, it doesn’t really look any different from your standard Spring MVC controller, but after reading through the methods, we can see the different return types from what we would normally expect. In this example, PersonRepository must be a reactive repository, as we have been able to directly return the results of their search queries. For reference, reactive repositories will return a Flux for collections and a Mono for singular entities.

The annotation method is not what I want to focus on in this post, though. It’s not cool and hip enough for us. There isn’t enough use of lambdas to satisfy our thirst for writing Java in a more functional way. But Spring WebFlux has our backs. It provides an alternative method to route and handle requests to our servers that lightly uses lambdas to write router functions. Let’s take a look at an example.

@Configuration
public class PersonRouter {

    @Bean
    public RouterFunction<ServerResponse> route(PersonHandler personHandler) {
        return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
            .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
            .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)
            .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)
            .andRoute(DELETE("/people/{id}"), personHandler::delete)
            .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry);
    }
}


These are all the routes to methods in the PersonHandler, which we will look at later on. We have created a bean that will handle our routing. To set up the routing functions, we use the well-named RouterFunctions class, providing us with a load of static methods. But for now, we are only interested with its route method. Below is the signature of the route method:

public static <T extends ServerResponse> RouterFunction<T> route(
      RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
      // stuff
}


The method shows that it takes in a RequestPredicate along with a HandlerFunction and outputs a RouterFunction.

The RequestPredicate is what we use to specify behavior of the route, such as the path to our handler function, what type of request it is, and the type of input it can accept. Due to my use of static imports to make everything read a bit clearer, some important information has been hidden from you.

To create a RequestPredicate, we should use the RequestPredicates (plural), a static helper class providing us with all the methods we need. Personally, I recommend statically importing RequestPredicates. Otherwise, your code will be a mess due to the number of times you might need to make use of RequestPredicates static methods. In the above example, GET, POST, PUT, DELETE, accept, and contentType are all static RequestPredicates methods.

The next parameter is a HandlerFunction, which is a Functional Interface. There are three pieces of important information here. It has a generic type of <T extends ServerResponse>, its handle method returns a Mono<T>, and it takes in a ServerRequest. Using these, we can determine that we need to pass in a function that returns a Mono<ServerResponse> (or one of its subtypes). This obviously places a heavy constraint onto what is returned from our handler functions, as they must meet this requirement or they will not be suitable for use in this format.

Finally, the output is a RouterFunction. This can then be returned and will be used to route to whatever function we specified. But normally, we would want to route lots of different requests to various handlers at once, which WebFlux caters to. Due to route returning a RouterFunction and the fact that RouterFunction also has its own routing method available, andRoute, we can chain the calls together and keep adding all the extra routes that we require.

If we take another look back at the PersonRouter example above, we can see that the methods are named after the REST verbs such as GET and POST that define the path and type of requests that a handler will take. If we take the first GET request for example, it is routing to /people with a path variable name id (path variable denoted by {id}). Meanwhile, the type of the returned content, specifically APPLICATION_JSON (static field from MediaType) is defined using the accept method. If a different path is used, it will not be handled. If the path is correct but the Accept header is not one of the accepted types, then the request will fail.

Before we continue, I want to go over the accept and contentType methods. Both of these set request headers — accept matches to the Accept header and contentType to Content-Type. The Accept header defines what Media Types are acceptable for the response, as we were returning JSON representations of the Person object setting it to APPLICATION_JSON (application/json in the actual header) makes sense.

The Content-Type has the same idea, but instead describes what Media Type is inside the body of the sent request. That is why only the POST and PUT verbs have contentType included, as the others do not have anything contained in their bodies. DELETE does not include accept and contentType, so we can conclude that it is neither expecting anything to be returned nor including anything in its request body.

Now that we know how to set up the routes, let's look at writing the handler methods that deal with the incoming requests. Below is the code that handles all the requests from the routes that were defined in the earlier example.

@Component
public class PersonHandler {

  private final PersonManager personManager;

  public PersonHandler(PersonManager personManager) {
    this.personManager = personManager;
  }

  public Mono<ServerResponse> get(ServerRequest request) {
    final UUID id = UUID.fromString(request.pathVariable("id"));
    final Mono<Person> person = personManager.findById(id);
    return person
        .flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))
        .switchIfEmpty(notFound().build());
  }

  public Mono<ServerResponse> all(ServerRequest request) {
    return ok().contentType(APPLICATION_JSON)
        .body(fromPublisher(personManager.findAll(), Person.class));
  }

  public Mono<ServerResponse> put(ServerRequest request) {
    final UUID id = UUID.fromString(request.pathVariable("id"));
    final Mono<Person> person = request.bodyToMono(Person.class);
    return personManager
        .findById(id)
        .flatMap(
            old ->
                ok().contentType(APPLICATION_JSON)
                    .body(
                        fromPublisher(
                            person
                                .map(p -> new Person(p, id))
                                .flatMap(p -> personManager.update(old, p)),
                            Person.class)))
        .switchIfEmpty(notFound().build());
  }

  public Mono<ServerResponse> post(ServerRequest request) {
    final Mono<Person> person = request.bodyToMono(Person.class);
    final UUID id = UUID.randomUUID();
    return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri())
        .contentType(APPLICATION_JSON)
        .body(
            fromPublisher(
                person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class));
  }

  public Mono<ServerResponse> delete(ServerRequest request) {
    final UUID id = UUID.fromString(request.pathVariable("id"));
    return personManager
        .findById(id)
        .flatMap(p -> noContent().build(personManager.delete(p)))
        .switchIfEmpty(notFound().build());
  }

  public Mono<ServerResponse> getByCountry(ServerRequest serverRequest) {
    final String country = serverRequest.pathVariable("country");
    return ok().contentType(APPLICATION_JSON)
        .body(fromPublisher(personManager.findAllByCountry(country), Person.class));
  }
}


One thing that is quite noticeable is the lack of annotations. Bar the @Component annotation to auto create a PersonHandler bean, there are no other Spring annotations.

I have tried to keep most of the repository logic out of this class and have hidden any references to the entity objects by going via the PersonManager that delegates to the PersonRepository it contains. If you are interested in the code within PersonManager, then it can be seen here on my GitHub, further explanations about it will be excluded for this post so we can focus on WebFlux itself.

Ok, back to the code at hand. Let’s take a closer look at the get and post methods to figure out what is going on.

public Mono<ServerResponse> get(ServerRequest request) {
  final UUID id = UUID.fromString(request.pathVariable("id"));
  final Mono<Person> person = personManager.findById(id);
  return person
      .flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))
      .switchIfEmpty(notFound().build());
}


This method is for retrieving a single record from the database that backs this example application. Due to Cassandra being the database of choice, I have decided to use a UUID for the primary key of each record, this has the unfortunate effect of making testing the example more annoying but nothing that some copy and pasting can’t solve.

Remember that a path variable was included in the path for this GET request. Using the pathVariable method on the ServerRequest passed into the method, we are able to extract its value by providing the name of the variable, in this case id. The ID is then converted into a UUID, which will throw an exception if the string is not in the correct format. I decided to ignore this problem so the example code doesn’t get messier.

Once we have the ID, we can query the database for the existence of a matching record. A Mono<Person> is returned that either contains the existing record mapped to a Person or left as an empty Mono.

Using the returned Mono, we can output different responses depending on its existence. This means we can return useful status codes to the client to go along with the contents of the body. If the record exists, then flatMap returns a ServerResponse with the OK status.

Along with this status, we want to output the record. To do this, we specify the content type of the body, in this case APPLICATION_JSON, and add the record into it. fromPublisher takes our Mono<Person> (which is a Publisher) along with the Person class so it knows what it is mapping into the body. fromPublisher is a static method from the BodyInserters class.

If the record does not exist, then the flow will move into the switchIfEmpty block and return a NOT FOUND status. As nothing is found, the body can be left empty so we just create the ServerResponse there are then.

Now onto the post handler.

public Mono<ServerResponse> post(ServerRequest request) {
  final Mono<Person> person = request.bodyToMono(Person.class);
  final UUID id = UUID.randomUUID();
  return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri())
      .contentType(APPLICATION_JSON)
      .body(
          fromPublisher(
              person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class));
}


Even just from the first line, we can see that it is already different to how the get method was working. As this is a POST request, it needs to accept the object that we want to persist from the body of the request. As we are trying to insert a single record, we will use the request’s bodyToMono method to retrieve the Person from the body. If you were dealing with multiple records, you would probably want to use bodyToFlux instead.

We will return a CREATED status using the created method that takes in a URI to determine the path to the inserted record. It then follows a similar setup as the get method by using the fromPublisher method to add the new record to the body of the response. The code that forms the Publisher is slightly different, but the output is still a Mono<Person>, which is what matters. Just for further explanation about how the inserting is done, the Person passed in from the request is mapped to a new Person using the UUID we generated and is then passed to save by calling flatMap. By creating a new Person, we only insert values into Cassandra that we allow, in this case — we do not want the UUID passed in from the request body.

So, that’s about it when it comes to the handlers. Obviously, there other methods that we didn’t go through. They all work differently but all follow the same concept of returning a ServerResponse that contains a suitable status code and record(s) in the body if required.

We have now written all the code we need to get a basic Spring WebFlux back-end up a running. All that is left is to tie all the configuration together, which is easy with Spring Boot.

@SpringBootApplication
public class Application {
    public static void main(String args[]) {
        SpringApplication.run(Application.class);
    }
}


Rather than ending the post here, we should probably look into how to actually make use of the code.

Spring provides the WebClient class to handle requests without blocking. We can make use of this now as a way to test the application, although there is also a WebTestClient, which we could use here instead. The WebClient is what you would use instead of the blocking RestTemplate when creating a reactive application.

Below is some code that calls the handlers that were defined in the PersonHandler.

public class Client {

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

  public void doStuff() {

    // POST
    final Person record = new Person(UUID.randomUUID(), "John", "Doe", "UK", 50);
    final Mono<ClientResponse> postResponse =
        client
            .post()
            .uri("/people")
            .body(Mono.just(record), Person.class)
            .accept(APPLICATION_JSON)
            .exchange();
    postResponse
        .map(ClientResponse::statusCode)
        .subscribe(status -> System.out.println("POST: " + status.getReasonPhrase()));

    // GET
    client
        .get()
        .uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4")
        .accept(APPLICATION_JSON)
        .exchange()
        .flatMap(response -> response.bodyToMono(Person.class))
        .subscribe(person -> System.out.println("GET: " + person));

    // ALL
    client
        .get()
        .uri("/people")
        .accept(APPLICATION_JSON)
        .exchange()
        .flatMapMany(response -> response.bodyToFlux(Person.class))
        .subscribe(person -> System.out.println("ALL: " + person));

    // PUT
    final Person updated = new Person(UUID.randomUUID(), "Peter", "Parker", "US", 18);
    client
        .put()
        .uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc")
        .body(Mono.just(updated), Person.class)
        .accept(APPLICATION_JSON)
        .exchange()
        .map(ClientResponse::statusCode)
        .subscribe(response -> System.out.println("PUT: " + response.getReasonPhrase()));

    // DELETE
    client
        .delete()
        .uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc")
        .exchange()
        .map(ClientResponse::statusCode)
        .subscribe(status -> System.out.println("DELETE: " + status));
  }
}


Don’t forget to instantiate the Client somewhere. Below is a nice, lazy way to do it!

@SpringBootApplication
public class Application {
    public static void main(String args[]) {
        SpringApplication.run(Application.class);
        Client client = new Client();
        client.doStuff();
    }
}


First we create the WebClient.

private final WebClient client = WebClient.create("http://localhost:8080");


Once created we can start doing stuff with it, hence the doStuff method.

Let’s break down the POST request that is being send to the back-end.

final Mono<ClientResponse> postResponse =
    client
        .post()
        .uri("/people")
        .body(Mono.just(record), Person.class)
        .accept(APPLICATION_JSON)
        .exchange();
postResponse
    .map(ClientResponse::statusCode)
    .subscribe(status -> System.out.println("POST: " + status.getReasonPhrase()));


I wrote this one down slightly differently so you can see that a Mono<ClientResponse> is returned from sending a request. The exchange method fires the HTTP request over to the server. The response will then be dealt with whenever the response arrives, if it ever does.

Using the WebClient, we specify that we want to send a POST request using the post method, of course. The URI is then added with the uri method (overloaded method, this one takes in a String but another accepts a URI). I'm tired of saying this method does what the method is called so, the contents of the body are then added along with the Accept header. Finally, we send the request by calling exchange.

Note that the Media Type of APPLICATION_JSON matches up with the type defined in the POST router function. If we were to send a different type, say TEXT_PLAIN, we would get a 404 error, as no handler exists that matches what the request is expecting to be returned.

Using the Mono<ClientResponse> returned by calling exchange, we can map its contents to our desired output. In the case of the example above, the status code is printed to the console. If we think back to the post method in PersonHandler, remember that it can only return the “Created” status, but if the sent request does not match up correctly, then “Not Found” will be printed out.

Let’s look at one of the other requests.

client
    .get()
    .uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4")
    .accept(APPLICATION_JSON)
    .exchange()
    .flatMap(response -> response.bodyToMono(Person.class))
    .subscribe(person -> System.out.println("GET: " + person));


This is our typical GET request. It looks pretty similar to the POST request we just went through. The main differences are that uri takes in both the path of the request and the UUID (as a String in this case) as a parameter. That will replace the path variable {id} and the body is left empty. How the response is handled is also different. In this example, it extracts the body of the response and maps it to a Mono<Person> and prints it out. This could have been done with the previous POST example. but the status code of the response was more useful for its scenario.

For a slightly different perspective, we could use cURL to make requests and see what the response looks like.

CURL -H "Accept:application/json" -i localhost:8080/people


HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

[
  {
      "id": "13c403a2-6770-4174-8b76-7ba7b75ef73d",
      "firstName": "John",
      "lastName": "Doe",
      "country": "UK",
      "age": 50
  },
  {
      "id": "fbd53e55-7313-4759-ad74-6fc1c5df0986",
      "firstName": "Peter",
      "lastName": "Parker",
      "country": "US",
      "age": 50
  }
]


The response will look something like this, obviously it will differ depending on the data you have stored.

Note the response headers.

transfer-encoding: chunked
Content-Type: application/json


The transfer-encoding here represents data that is transferred in chunks that can be used to stream data. This is what we need so the client can act reactively to the data that is returned to it.

I think that this should be a good place to stop. We have covered quite a lot of material here, which has hopefully helped you understand Spring WebFlux better. There are a few other topics I want to cover about WebFlux, but I will do those in separate posts, as I think this one is long enough as it is.

In conclusion, in this post we very briefly discussed why you would want to use Spring WebFlux over a typical Spring MVC back-end. We then looked at how to set up routes and handlers to process the incoming requests. The handlers implemented methods that could deal with most of the REST verbs and returned the correct data and status codes in their responses. Finally, we looked at two ways to make requests to the back-end, one using a WebClient to process the output directly on the client side and another via cURL to see what the returned JSON looks like.

If you are interested in looking at the rest of the code I used to create the example application for this post, it can be found on my GitHub.

As always, if you found this post helpful, please share it and if you want to keep up with my latest posts then you can follow me on Twitter at @LankyDanDev.

Spring Framework Database Requests POST (HTTP) Spring Boot application

Published at DZone with permission of Dan Newton, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How To Build Web Service Using Spring Boot 2.x
  • How To Build Self-Hosted RSS Feed Reader Using Spring Boot and Redis
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

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!