Sometimes, your Spring Boot application needs to fetch data from another service instead of a database. That's when you should use WebClient.

If you're unfamiliar with WebClient, it's part of the Spring WebFlux stack. And it's a great way to integrate with another REST API.

In this guide, I'll show you how to use WebClient for that very purpose.

You can follow along here. Or you can grab the code on GitHub. But there's a lot more on GitHub than you'll need so it's probably best to just follow along.

The Business Requirements

Your boss Smithers walks into your office and starts mumbling something about the trouble with microservices.

"About that CRM you're working on," he says with a sigh. "We need the contact service to access data from the user database as well."

He sniffles loudly.

"Put together a service-to-service integration," he finally says. "Make the contact service get sales owner data from the user service. You can do that, right?"

You nod.

He walks out of your office mumbling something about a new set of curtains.

Fluxing Your Muscles

As I mentioned above, WebClient is part of the Spring Webflux stack. So you're going to need a new dependency in pom.xml.

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

You don't need to specify a version there because Spring will just grab the version associated with the Spring Boot parent.

So what is this WebFlux thing? In a nutshell, it's a way to handle reactive processing.

If you're not familiar with reactive processing, it's a software development paradigm that essentially tells your application to go do something without waiting for a response.

You see that all the time in Angular development. Every time you use an Observer, you're doing reactive processing.

WebFlux is that same concept on the back end instead of the front end. It enables your application to go to a downstream service and grab a response while it goes on merrily handling other tasks in the meantime. 

You can use WebClient for reactive processing. But there's another reason to use it as well.

Rest in Peace, RestTemplate

RestTemplate is what developers used to use to handle service-to-service integration. But now RestTemplate is gone.

As in: bye-bye. Finito. Over and out. Eliminated.

Okay, they use the word "deprecated" over at Spring headquarters. But for practical purposes, you shouldn't be using it any more.

So use WebClient instead.

Now that we've got the background out of the way, it's time to do some coding.

The Client in the Class

The first thing you need to do is instantiate the WebClient class. There are a number of ways to do that but here's what I think you should do in UserService.

First, create a property that holds the client for all requests.

    private WebClient userClient; 

That way you don't have to recreate the WebClient instance every time a client makes a request.

By the way, that means there's a 1:1 relationship here between the WebClient object and the downstream service. If you want to call more than one service, you'll need to use more than one client.

Next, code the constructor.

    public UserService(@Value("${ecosystem.properties.file.location}") String ecosystemFile) {
        PropertiesUtil propertiesUtil = new PropertiesUtil(ecosystemFile);
        String endpoint = propertiesUtil.getProperty("ecosystem-user-service.endpoint");
        userClient = WebClient.create(endpoint);
    }

The whole class, as you can't see above, is annotated with @Service. So it's a Spring-managed component.

That's why the code gets away with using @Value in the constructor. The framework is smart enough to go to application.properties and get the value of ecosystem.properties.file.location

That string points to a location (for example, "/etc/careydevelopment/ecosystem.properties") where it will find a simple properties file. Then, it uses PropertiesUtil to read the value of ecosystem-user-service.endpoint in that file.

That string tells the Spring Boot app where to find the downstream service. It might be something like http://my.user-service.com.

However, that's just the server and context. It's not the full path for getting users. You'll assign the full path when you make a call.

But the big takeaway here is that last line. The code above uses the static method WebClient.create() to instantiate WebClient.

GETting to the Bottom

Now that you have an instance of WebClient, it's easy to call the downstream service to get a JSON object. Here's the method that handles retrieving a single user:

    public SalesOwner fetchUser(HttpServletRequest request) {
        final String requestTokenHeader = request.getHeader("Authorization");
        
        SalesOwner salesOwner = userClient.get()
                .uri("/user/me")
                .header(HttpHeaders.AUTHORIZATION, requestTokenHeader)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(SalesOwner.class)
                .block();
        
        LOG.debug("User is " + salesOwner);
        
        return salesOwner;
    }

The first thing you'll notice is that the method accepts an HttpServletRequest object. That's because the code needs access to the bearer token so it can pass it to the downstream service.

All the services are hardened with security in this ecosystem. So every request needs that bearer token.

The next line (which is really the next several lnes) uses the WebClient object to make the call to the service.

First, note that the code invokes the get() method on the WebClient instance. That's because it's making a simple HTTP GET request (a read instead of a create, update, or delete).

But then the code sets the full endpoint with the uri() method. In the example above, the full path ends with /user/me.

So if the base URL is http://my.user-service.com as I mentioned above, then the full endpoint for this GET request is http://my.user-service.com/user/me. 

The code also plops the bearer token in the Authorization header with the aid of the header() method you see above.

Additionally, the method sets the Accept header as well. It could do that with another header() method but instead it uses the shortcut accept() method.

Then it calls retrieve(). What the heck is going on there?

That's the starting point for telling WebClient how to parse the response. In this case, the "how" answer is in the next line.

That brings us to bodyToMono(). That's a pretty weird method name, isn't it?

What that name means, in a nutshell, is "convert the response body to a Mono type."

And what's a Mono type? It's when the client is expecting 0 or 1 results. It stands in contrast to a Flux response which expects 0 to many results.

In this use case, there should only be one sales owner who matches the ID that gets sent with the bearer token. So a Mono fits perfectly here.

But it's not good enough to say, "I just want 0 or 1 objects." You also need to tell the application what the object should look like.

That's why you see SalesOwner.class as the sole parameter in bodyToMono().

Here's what the code is saying there: "I'm going to make a call that returns a JSON object and you're going to translate that JSON object to a Java object by matching properties in the JSON body to fields in the Java class."

Now why is that block() thing hanging around at the end?

It's telling the application to block all processing until it gets the response. In other words, the code is not using the Observer pattern here. 

But that's usually just fine for service-to-service integrations. It's often the case that you want the application to wait for a response and then return it to the client.

Testing Time

Now save the code, launch the Spring Boot application, and test this thing with Postman. You can check that log statement above to make sure it got the SalesOwner object back.

This method in UserService gets called with POST request that creates a new contact in a CRM app. So to check if it works, you need to make that kind of request.

When I do a POST on http://localhost:32020/contact, I see the following in the log:

User is com.careydevelopment.contact.model.SalesOwner@62f80189[id=5f78d,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212]

Excellent. It's working as designed.

The More, the Merrier

That solution works if you're only interested in retrieving a single user. But what if you want to retrieve many? 

In fact, what if you want to retrieve all users?

You can do that with WebClient as well:

        List<SalesOwner> salesOwners = userClient.get()
                .uri("/user/all")
                .header(HttpHeaders.AUTHORIZATION, requestTokenHeader)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<SalesOwner>>() {})
                .block();

In this case, I've created a downstream endpoint (/user/all) that returns all users. You'll see that new endpoint in the uri() method above.

The code also uses a new parameter in the bodyToMono() method. What it's saying there is: "I'm expecting an array of JSON objects that will get translated to SalesOwner Java objects."

That ParameterizedTypeReference you see there is necessary because of type erasure. Without that, the code won't be able to keep the type specification at runtime.

Now if I run my test again, I'll see this in the log:

Users are [com.careydevelopment.contact.model.SalesOwner@1bf4f911[id=5f78,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212], com.careydevelopment.contact.model.SalesOwner@39690874[id=5fc95,firstName=Hot,lastName=Mon,email=hotmon@xmail.com,username=hotmon,phoneNumber=474-555-1212]]

As you can see, the application returned both users in the database.

But you might be looking at the code and asking yourself: "Hey, why doesn't that code use a Flux? After all, it's looking for many objects and not just one!"

And you'd be right.

In fact, you can use bodyToFlux() above. And then you can call collectList() on what that returns.

That will work. But since you don't want to stream the results and instead just want to parse a list, you don't need to go that route.

Wrapping It Up

And there you have it: a nice, neat way to use WebClient for service-to-service integration.

Now it's up to you to adapt the code you've seen here to your own requirements. Also, try using WebClient with other HTTP methods, such as PUT, POST, and DELETE.

As always, you can have a look at the code I've used on GitHub. Feel free to fork it and update as needed.

Have fun!

Photo by fauxels from Pexels