How to write a generic REST client in Spring Boot?

We live in the world of microservices.

REST APIs are all over and they communicate with each other.

To communicate with each other in the Spring World , they use RestTemplate or Spring WebClient or Java’s own HttpClient(Java 11) or any other third party libraries.

We call them REST clients.

It will be nice if we write as little code as possible while using any of these REST clients.

And Generics helps us in doing that.

Let’s see how to use generics to write a generic REST client.

Assume three APIS are deployed on a server.

For this example ,I have just deployed them in my local machine.

Three of them take different types of inputs and return different types of responses.

And we are going to use a single method to call them all using generics!

Advertisements

First let’s do the traditional way:

Here are the three APIs deployed in my local server at port 8080:

  1. http://localhost:8080/getMessage

This takes a string as an input and returns a string as an output

Code:

@PostMapping("/getMessage")
	public String getMessage(@RequestBody String messageId) {

		return "Hello Generics";
	}

2. http://localhost:8080/getMessageMap:

This takes a map as input and returns a map output

Code:

	@PostMapping("/getMessageMap")
	public Map<String, String> getMessageMap(@RequestBody Map<String,String> input) {

		Map<String, String> map = new HashMap<>();
		map.put("message", "Response to your request "+input.get("request")+" is 'Hello Generics'");
		return map;
	}

3. http://localhost:8080/getMessageObject

This takes a java bean object as input and java bean object as output

Code:

	@PostMapping("/getMessageObject")
	public MyResponseObject getMessageObject(@RequestBody MyRequestObject request) {

		MyResponseObject response = new MyResponseObject();
		response.setMessage("Response to your message '" +request.getMessage()+"' is 'Hello Generics'");
		return response;
	}

The custom request object:

package com.example.generics;

public class MyRequestObject {

	
	private int id;
	private String message;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	
	
}

The custom response object:

package com.example.generics;

public class MyResponseObject {

	
	
	private String message;

	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	@Override
	public String toString() {
		return "MyResponseObject [message=" + message + "]";
	}
	
	
}

Advertisements

Now , to call these REST APIs from another microservice application (let’s say using Spring WebClient) , traditionally you can use the below REST client code:

package com.example.generics;

import java.util.Map;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

@Component
public class RestClient {

	public String getMessage(String input) {

		String response = WebClient.builder().build().post().uri("http://localhost:8080/getMessage")
				.body(BodyInserters.fromValue(input)).retrieve().bodyToMono(String.class).block();

		return response;

	}

	public Map<String, String> getMessageMap(Map<String, String> input) {

		Map<String, String> response = WebClient.builder().build().post().uri("http://localhost:8080/getMessageMap")
				.body(BodyInserters.fromValue(input)).retrieve().bodyToMono(Map.class).block();

		return response;

	}

	public MyResponseObject getMessage(MyRequestObject input) {

		MyResponseObject response = WebClient.builder().build().post().uri("http://localhost:8080/getMessageObject")
				.body(BodyInserters.fromValue(input)).retrieve().bodyToMono(MyResponseObject.class).block();

		return response;

	}
}

Three different methods are used to call the three different APIs as their input and output types are different.

But if you notice the lines look duplicate except for the REQUEST and RESPONSE types.

What if there is a way to generalize the request and response types.

Generics offers the way.

Here is how you can refactor the above class using a single method using Generics:

package com.example.generics;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

@Component
public class GenericRestClient {

	public <K, T> K post(String url, T request, Class<K> clazz) {

		K response =  WebClient.builder().build().post().uri(url).body(BodyInserters.fromValue(request)).retrieve().bodyToMono(clazz).block();
		
		return response;

	}
}

Much shorter!

And just a single method.

Here is what I am doing here

The request type is represented by a generic data type “T”

The response type is represented by a generic data type “K”

You mention these two in angular brackets at the start of the method declaration:

public <K, T> K post(String url, T request, Class<K> clazz) {

		K response =  WebClient.builder().build().post().uri(url).body(BodyInserters.fromValue(request)).retrieve().bodyToMono(clazz).block();
		
		return response;

	}

The response type of the method is the generic data type “K” , you write this after the angular brackets.

You need to pass the REST API url along with the request and the response class type in the method arguments.

The method bodyToMono() inside the method post() accepts a class of the response type and so we pass the parameter of type Class<K>

Now let’s invoke the above two types of rest clients and see if they work.

Here is the code using both the rest clients:

package com.example.generics;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GenericsApplication implements ApplicationRunner {

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

	@Autowired
	private RestClient client;

	@Autowired
	private GenericRestClient genericClient;

	@Override
	public void run(ApplicationArguments args) throws Exception {

		// ******************** WITHOUT GENERICS *********************//

		String response = client.getMessage("hello");
		System.out.println(response);

		Map<String, String> request = new HashMap<>();
		request.put("message", "hello");
		Map responseMap = client.getMessageMap(request);
		System.out.println(responseMap);

		MyRequestObject requestObj = new MyRequestObject();
		requestObj.setId(1);
		requestObj.setMessage("Hello");

		MyResponseObject responseObj = client.getMessage(requestObj);

		System.out.println(responseObj);

		// ****************** USING GENERICS **************************//

		
		String response2 = genericClient.post("http://localhost:8080/getMessage","hello",String.class);
		System.out.println(response2);


		Map responseMap2 =genericClient.post("http://localhost:8080/getMessageMap", request, Map.class);
		System.out.println(responseMap2);


		MyResponseObject responseObj2 = genericClient.post("http://localhost:8080/getMessageObject", requestObj, MyResponseObject.class);

		System.out.println(responseObj2);

	}

}

I have run the code using Spring Boot ApplicationRunner .

Here is the output:

Both the clients print the same output.

Link to github code:

https://github.com/vijaysrj/generics

That’s it!

There are more ways to call REST APIs too.

One of them is Spring Open Feign , where you just need to declare interfaces and Spring will take care of all the code implementation.

Here is an article on it:

Open Feign

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s