RestTemplate vs. WebClient
Take a look at this comparison for Spring framework's two web client implementation, RestTemplate and WebClient, based on a couple criteria.
Join the DZone community and get the full member experience.
Join For FreeIn this tutorial, we will compare two of Spring framework's provided web client implementations:
- RestTemplate
- WebClient, Spring 5's reactive alternative
What Are RestTemplate and WebClient?
RestTemplate is the central class within the Spring framework for executing synchronous HTTP requests on the client side.
WebClient is a reactive client for performing HTTP requests with Reactive Streams back pressure.
How Do RestTemplate and WebClient Differ From Each Other?
Thread Safety
RestTemplate and WebClient both are thread safe, but each one implements it differently.
RestTemplate
RestTemplate is thread-safe once constructed. Objects of the RestTemplate class do not change any of their state information to process HTTP: the class is an instance of the Strategy design pattern. With no state information, there is no possibility of different threads corrupting or racing state information if they share a RestTemplate object. This is why it is possible for threads to share these objects. Let's see this with following example.
/* Main application class */
public class RestTemplateTest {
public static void main(String[] args) {
ThreadExecutor threadEx = new ThreadExecutor();
for (int i = 0; i < 4; i++) {
System.out.println("\n"+ threadEx.getData());
}
}
}
/* Thread executor class */
public class ThreadExecutor {
private ExecutorService executor = Executors.newFixedThreadPool(10);
private RestTemplate restTemplate = new RestTemplate();
public String getData() {
System.out.println("\nRestTemplate obj details - "+restTemplate.toString() + "\n");
Future<String> future = executor.submit(new Task(restTemplate));
String response = null;
try {
response = future.get(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return response;
}
}
/* Task class, calls /person api */
class Task implements Callable<String> {
private RestTemplate restTemplate;
public Task(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String call() throws Exception {
String url = "http://localhost:8000/person/1";
String response = restTemplate.getForObject(url, String.class);
return response;
}
}
/* Controller */
@RestController
class PersonRestController {
@GetMapping("/person/{id}")
public Person findPersonById(@PathVariable("id") Long id) {
return new Person(1L, "Heena", "Khan");
}
@GetMapping("/person")
public List<Person> getAllPersonList() {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person(10L, "Heena", "Khan"));
personList.add(new Person(20L, "Kiara", "Khan"));
personList.add(new Person(30L, "Ayaan", "Shaikh"));
return personList;
}
}
/* Entity class */
class Person {
Person(Long id, String name, String surname) {
this.id = id;
this.name = name;
this.surname = surname;
}
private Long id;
private String name;
private String surname;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
OUTPUT :
RestTemplate obj details - org.springframework.web.client.RestTemplate@124c278f
22:39:04.127 [pool-1-thread-1] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8000/person/1
22:39:04.136 [pool-1-thread-1] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
22:39:04.156 [pool-1-thread-1] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
22:39:04.157 [pool-1-thread-1] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
{"id":1,"name":"Heena","surname":"Khan"}
RestTemplate obj details - org.springframework.web.client.RestTemplate@124c278f
22:39:04.160 [pool-1-thread-2] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8000/person/1
22:39:04.161 [pool-1-thread-2] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
22:39:04.165 [pool-1-thread-2] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
22:39:04.165 [pool-1-thread-2] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
{"id":1,"name":"Heena","surname":"Khan"}
RestTemplate obj details - org.springframework.web.client.RestTemplate@124c278f
22:39:04.166 [pool-1-thread-3] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8000/person/1
22:39:04.166 [pool-1-thread-3] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
22:39:04.167 [pool-1-thread-3] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
22:39:04.167 [pool-1-thread-3] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
{"id":1,"name":"Heena","surname":"Khan"}
RestTemplate obj details - org.springframework.web.client.RestTemplate@124c278f
22:39:04.168 [pool-1-thread-4] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8000/person/1
22:39:04.168 [pool-1-thread-4] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
22:39:04.170 [pool-1-thread-4] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
22:39:04.171 [pool-1-thread-4] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
{"id":1,"name":"Heena","surname":"Khan"}
If we observe the above output, all /person/{id}
API call are using same RestTemplate object.
Accessing a third-party REST service inside a Spring application revolves around the use of the Spring RestTemplate class. The RestTemplate class is designed on the same principles as the many other Spring *Template classes (e.g., JdbcTemplate, JmsTemplate ).
Supported methods are:
- getForObject(url, classType) – retrieve a representation by doing a GET on the URL. The response (if any) is unmarshalled to given class type and returned.
- getForEntity(url, responseType) – retrieve a representation as ResponseEntity by doing a GET on the URL.
- exchange(requestEntity, responseType) – execute the specified request and return the response as ResponseEntity.
- execute(url, httpMethod, requestCallback, responseExtractor) – execute the httpMethod to the given URI template, preparing the request with the RequestCallback, and reading the response with a ResponseExtractor.
WebClient
WebClient is thread-safe because it is immutable. WebClient is meant to be used in a reactive environment, where nothing is tied to a particular thread. Here we see how to create a WebClient instance, and use it to build and execute an HTTP request. Spring WebClient is a non-blocking, reactive client to perform HTTP requests, a part of Spring Webflux framework.
The create(baseUrl) method is most commonly used to create WebClient instance because it is handy and we can provide the default base URL for subsequence requests later.
xxxxxxxxxx
WebClient webClient = WebClient.create("http://localhost:8080");
We can use builder() if we want to set the default headers and cookies.
xxxxxxxxxx
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultHeader(HttpHeaders.USER_AGENT, "Hello World")
.defaultCookie("cookie name", "cookie value")
.build();
Build an HTTP request with WebClient. The following gives you an example on building a GET request.
xxxxxxxxxx
public void buildAnHttpRequest() {
WebClient webClient = WebClient.create("http://localhost:8080");
/* Mono is a stream which returns zero items or a single item */
Mono<String> result = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/hello")
.queryParam("name", "Heena")
.build())
.retrieve()
.bodyToMono(String.class);
assertThat(result.block())
.isEqualTo("Hello, Heena!");
}
Blocking vs. Non-Blocking Client
RestTemplate
RestTemplate is synchronous in nature, using a Thread-per-Request method. Until each request is completed and response is sent back to user or service erred out, the thread will be blocked out for that user. Below is an example of synchronous call.
xxxxxxxxxx
public class BlockingMechanism {
public static void main(String[] args) {
System.out.println("Starting BLOCKING mechanism...");
final String uri = "http://localhost:8000/person";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Person>> response = restTemplate.exchange(
uri, HttpMethod.GET, null,
new ParameterizedTypeReference<List<Person>>(){});
List<Person> result = response.getBody();
result.forEach(person -> System.out.println(person.toString()));
System.out.println("Exiting BLOCKING mechanism...");
}
}
OUTPUT :
Starting BLOCKING mechanism...
12:26:14.725 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8000/person
12:26:14.761 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/json]
12:26:15.064 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
12:26:15.077 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.util.List<hello.Person>]
Person [id=10, name=Heena, surname=Khan]
Person [id=20, name=Kiara, surname=Khan]
Person [id=30, name=Ayaan, surname=Shaikh]
Exiting BLOCKING mechanism...
WebClient
WebClient works on the concept of asynchronous and non-blocking strategy.
xxxxxxxxxx
public class NonBlockingMechanism {
public static void main(String[] args) {
System.out.println("Starting NON-BLOCKING mechanism...");
Flux<Person> PersonFlux = WebClient.create()
.get()
.uri("http://localhost:8000/person")
.retrieve()
.bodyToFlux(Person.class);
PersonFlux.subscribe(Person -> System.out.println(Person.toString()));
System.out.println("Exiting NON-BLOCKING mechanism...");
System.out.println(PersonFlux.blockFirst().toString());
}
}
OUTPUT :
Starting NON-BLOCKING mechanism...
12:36:13.082 [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
12:36:13.104 [main] DEBUG io.netty.util.internal.PlatformDependent - Platform: Windows
12:36:13.107 [main] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
12:36:13.108 [main] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 13
12:36:13.109 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
12:36:13.110 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
12:36:13.110 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
12:36:13.113 [main] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable
...
Exiting NON-BLOCKING mechanism...
12:36:14.621 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [15d49048] HTTP GET http://localhost:8000/person
12:36:14.631 [reactor-http-nio-2] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x31fc4f5b] Created a new pooled channel, now 2 active connections and 0 inactive connections
12:36:14.631 [reactor-http-nio-1] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x80cf000e] Created a new pooled channel, now 2 active connections and 0 inactive connections
12:36:14.778 [reactor-http-nio-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [15d49048] Response 200 OK
12:36:14.876 [reactor-http-nio-1] DEBUG reactor.netty.channel.FluxReceive - [id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000] Subscribing inbound receiver [pending: 0, cancelled:false, inboundDone: false]
12:36:14.876 [reactor-http-nio-2] DEBUG reactor.netty.channel.FluxReceive - [id: 0x31fc4f5b, L:/127.0.0.1:63238 - R:localhost/127.0.0.1:8000] Subscribing inbound receiver [pending: 0, cancelled:false, inboundDone: false]
12:36:14.891 [reactor-http-nio-1] DEBUG org.springframework.http.codec.json.Jackson2JsonDecoder - [15d49048] Decoded [Person [id=10, name=Heena, surname=naaz]]
12:36:14.891 [reactor-http-nio-2] DEBUG org.springframework.http.codec.json.Jackson2JsonDecoder - [15d49048] Decoded [Person [id=10, name=Heena, surname=naaz]]
Person [id=10, name=Heena, surname=naaz]
12:36:14.892 [reactor-http-nio-1] DEBUG org.springframework.http.codec.json.Jackson2JsonDecoder - [15d49048] Decoded [Person [id=20, name=Kiara, surname=khan]]
Person [id=20, name=Kiara, surname=khan]
12:36:14.892 [reactor-http-nio-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [15d49048] Cancel signal (to close connection)
12:36:14.892 [reactor-http-nio-1] DEBUG org.springframework.http.codec.json.Jackson2JsonDecoder - [15d49048] Decoded [Person [id=30, name=Ayaan, surname=Shaikh]]
Person [id=30, name=Ayaan, surname=Shaikh]
12:36:14.892 [reactor-http-nio-1] DEBUG reactor.netty.http.client.HttpClientOperations - [id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000] Received last HTTP packet
12:36:14.893 [reactor-http-nio-1] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000] onStateChange(GET{uri=/person, connection=PooledConnection{channel=[id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000]}}, [disconnecting])
12:36:14.893 [reactor-http-nio-1] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000] Releasing channel
12:36:14.896 [reactor-http-nio-1] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x80cf000e, L:/127.0.0.1:63237 - R:localhost/127.0.0.1:8000] Channel cleaned, now 0 active connections and 1 inactive connections
12:36:14.900 [reactor-http-nio-2] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x31fc4f5b, L:/127.0.0.1:63238 ! R:localhost/127.0.0.1:8000] Channel closed, now 0 active connections and 1 inactive connections
Person [id=10, name=Heena, surname=naaz]
12:36:14.901 [reactor-http-nio-2] DEBUG reactor.netty.http.client.HttpClientOperations - [id: 0x31fc4f5b, L:/127.0.0.1:63238 ! R:localhost/127.0.0.1:8000] Received last HTTP packet
12:36:14.901 [reactor-http-nio-2] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x31fc4f5b, L:/127.0.0.1:63238 ! R:localhost/127.0.0.1:8000] onStateChange(GET{uri=/person, connection=PooledConnection{channel=[id: 0x31fc4f5b, L:/127.0.0.1:63238 ! R:localhost/127.0.0.1:8000]}}, [disconnecting])
12:36:14.901 [reactor-http-nio-2] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x31fc4f5b, L:/127.0.0.1:63238 ! R:localhost/127.0.0.1:8000] Releasing channel
In the above output,
- ExchangeFunctions - Represents a function that exchanges a request for a (delayed) ClientResponse.
- PooledConnectionProvider - Will produce Connection.
- FluxReceive - Subscribes inbound receiver.
Here each request will be treated as a task. all requests (which are waiting for response) are getting added in a queue, mean while the serving thread will be unblocked to serve other requests. When the response is received for the waiting task, WebClient will activate that task by assigning thread to it and resume the rest execution.
Configuration
RestTemplate
To make use of RestTemplate ,we need the following spring-web artifact that contains RestTemplate class.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
WebClient
To make use of WebClient, we need the following dependency in your Spring Boot project.
xxxxxxxxxx
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
RestTemplate is getting deprecated and will not be supported in future versions of Spring.
We should start using WebClient. It not only supports Asynchronous it also supports Synchronous calls so we call use WebClient as a replacement for RestTemplate.
Opinions expressed by DZone contributors are their own.
Comments