To switch from using RestTemplate
to using WebClient
in a Java 21 project with Project Reactor, you’ll need to take a few steps. WebClient is a non-blocking, reactive HTTP client that's part of the Spring WebFlux module, and it integrates seamlessly with reactive programming paradigms.
Here’s an example of how you can migrate from RestTemplate
to WebClient
in your Java project. I'll walk you through both basic and advanced usage of WebClient
to call an API.
Step 1: Add Required Dependencies
If you haven’t already added the necessary dependencies for WebClient in your pom.xml
(for Maven) or build.gradle
(for Gradle), do so first.
For Maven:
xml<dependencies>
<!-- Spring WebFlux for WebClient -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Optional: For JSON parsing (Jackson or Gson) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
For Gradle:
gradledependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'com.fasterxml.jackson.core:jackson-databind' // Optional }
Step 2: Set Up WebClient
Bean (optional, for Spring Boot)
If you're using Spring Boot, it's best to create a WebClient
bean so you can inject it into your service classes.
javaimport org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.create("https://api.example.com"); // Base URL for API calls
}
}
Step 3: Basic API Call Using WebClient
Assuming that you're calling an API that returns a JSON response, here’s how to replace a RestTemplate
call with WebClient
.
Example of RestTemplate
(Synchronous)
javaimport org.springframework.web.client.RestTemplate;
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String fetchData() {
String url = "https://api.example.com/data";
return restTemplate.getForObject(url, String.class);
}
}
Example of WebClient
(Asynchronous/Reactive)
javaimport org.springframework.web.reactive.function.client.WebClient;import reactor.core.publisher.Mono;
public class MyService {
private final WebClient webClient;
public MyService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> fetchData() {
String url = "/data"; // WebClient already has the base URL set from config
return webClient.get()
.uri(url)
.retrieve() // Executes the request
.bodyToMono(String.class); // Returns the response as a Mono (reactive type)
}
}
Key Differences:
RestTemplate
is synchronous and blocks the thread until a response is received.WebClient
is non-blocking and returns aMono
orFlux
, which is a reactive type in Project Reactor.
Step 4: Handle Errors and Status Codes
Handling errors in WebClient
is different than RestTemplate
. WebClient provides more fine-grained control over HTTP errors through onStatus()
.
Here’s how you can handle errors using WebClient
:
javapublic Mono<String> fetchData() {
String url = "/data"; // Base URL is pre-configured
return webClient.get()
.uri(url)
.retrieve()
.onStatus(
HttpStatus::is4xxClientError, // Handle 4xx status codes
clientResponse -> Mono.error(new RuntimeException("Client error occurred"))
)
.onStatus(
HttpStatus::is5xxServerError, // Handle 5xx status codes
clientResponse -> Mono.error(new RuntimeException("Server error occurred"))
)
.bodyToMono(String.class)
.doOnTerminate(() -> System.out.println("Request completed"));
}
Step 5: Handling JSON Responses
In case the API returns a JSON response, you can deserialize it into a POJO (Plain Old Java Object) with WebClient.
Example Response DTO (POJO):
javapublic class ApiResponse {
private String field1;
private int field2;
// Getters and setters
}
Fetching and Mapping JSON to POJO
javapublic Mono<ApiResponse> fetchData() {
String url = "/data"; // WebClient is already configured with base URL
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(ApiResponse.class); // Deserialize into ApiResponse
}
Step 6: Example of Blocking the Response (Not Recommended in a Reactive Application)
If you need the result in a blocking way (e.g., in a non-reactive part of your application), you can call .block()
to wait for the response. However, this is discouraged in fully reactive applications as it goes against the non-blocking principles.
javapublic String fetchDataBlocking() {
return webClient.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class)
.block(); // Blocks until response is received
}
Step 7: Full Example
Here’s a full example combining configuration and service usage:
javaimport org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@SpringBootApplication
public class WebClientExample {
public static void main(String[] args) {
SpringApplication.run(WebClientExample.class, args);
}
@Bean
public WebClient webClient() {
return WebClient.create("https://api.example.com");
}
@Bean
public CommandLineRunner run(WebClient webClient) {
return args -> {
MyService service = new MyService(webClient);
service.fetchData().subscribe(
response -> System.out.println("Response: " + response),
error -> System.err.println("Error: " + error.getMessage())
);
};
}
static class MyService {
private final WebClient webClient;
public MyService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> fetchData() {
return webClient.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class)
.doOnTerminate(() -> System.out.println("Request completed"));
}
}
}
Conclusion
Migrating from RestTemplate
to WebClient
is a great way to move towards a non-blocking, reactive approach in your applications. The WebClient
API gives you more flexibility, error handling capabilities, and is more efficient for handling large amounts of concurrent request
Also, you can absolutely use WebClient
in a non-Spring Boot application. The Spring WebFlux framework and WebClient
can be used independently of Spring Boot. You just need to manually configure a WebClient
instance and the necessary parts of the Spring WebFlux environment.
Steps to Use WebClient
Without Spring Boot
Below is an example of how to use WebClient
in a standard Java project (without Spring Boot).
Step 1: Add Dependencies
First, you need to include the necessary dependencies for WebClient
and Spring WebFlux. This requires adding spring-webflux
and reactor-core
as dependencies.
For Maven:
xml<dependencies>
<!-- Spring WebFlux for WebClient -->
<dependency>
<groupId>org.springframework.webflux</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.3.x</version> <!-- Use the latest version -->
</dependency>
<!-- Project Reactor (already included with spring-webflux, but you can explicitly add it if needed) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.x</version> <!-- Use the latest version -->
</dependency>
<!-- For JSON deserialization, you might want Jackson (or Gson) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.x</version> <!-- Use the latest version -->
</dependency>
</dependencies>
For Gradle:
gradledependencies { implementation 'org.springframework.webflux:spring-webflux:5.3.x' // Replace with the latest version implementation 'io.projectreactor:reactor-core:3.4.x' // Replace with the latest version implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.x' // Jackson for JSON }
Step 2: Set Up WebClient Without Spring Boot
In a non-Spring Boot application, you will manually create and configure a WebClient
instance. Below is a simple example of how to configure and use WebClient
for making HTTP requests.
Example: Basic API Call with WebClient
javaimport org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientExample {
public static void main(String[] args) {
// Step 1: Create a WebClient instance
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
// Step 2: Make a GET request and retrieve the response as a String
Mono<String> responseMono = webClient.get()
.uri("/posts/1")
.retrieve() // Executes the request
.bodyToMono(String.class); // Converts the body to String
// Step 3: Subscribe to get the response (blocking here for demo purposes)
responseMono.subscribe(response -> {
System.out.println("Response: " + response);
});
// Optional: Keep the application alive to see the result (since WebClient is asynchronous)
try {
Thread.sleep(3000); // Wait for async result
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Step 3: Handle JSON Responses
You can also map the response directly into a POJO (Plain Old Java Object) if the response is in JSON format.
Example Response DTO:
javapublic class Post {
private int id;
private String title;
private String body;
// Getters and setters
}
Example of Fetching JSON and Mapping to a POJO:
javaimport org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientExample {
public static void main(String[] args) {
// Step 1: Create WebClient instance
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
// Step 2: Make a GET request to fetch a post and convert it into a Post object
Mono<Post> postMono = webClient.get()
.uri("/posts/1")
.retrieve()
.bodyToMono(Post.class); // Deserialize JSON to Post class
// Step 3: Subscribe and handle the response
postMono.subscribe(post -> {
System.out.println("Post ID: " + post.getId());
System.out.println("Post Title: " + post.getTitle());
System.out.println("Post Body: " + post.getBody());
});
// Optional: Keeping application alive to see the result (since WebClient is asynchronous)
try {
Thread.sleep(3000); // Wait for async result
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Step 4: Running a Reactor Event Loop (Needed for WebClient
)
Since WebClient
is non-blocking and uses the Project Reactor event loop, you need to ensure that the event loop runs while you are waiting for the response. In the above examples, Thread.sleep()
is used just to make the example run. However, this is just for demonstration.
In a real application, you may need to use Reactor
's own scheduling mechanism for running asynchronous tasks.
Here’s a basic way to ensure the event loop runs while your application works:
javaimport reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class WebClientExample {
public static void main(String[] args) {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
Mono<Post> postMono = webClient.get()
.uri("/posts/1")
.retrieve()
.bodyToMono(Post.class);
// Run the Mono asynchronously on a separate thread (to simulate real-world async usage)
postMono.subscribeOn(Schedulers.elastic())
.subscribe(post -> {
System.out.println("Post ID: " + post.getId());
System.out.println("Post Title: " + post.getTitle());
System.out.println("Post Body: " + post.getBody());
});
// Keep application alive to see result
try {
Thread.sleep(3000); // Wait for async result
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Step 5: Error Handling and Logging
You can also apply error handling and logging as needed, for example:
javaimport org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientExample {
public static void main(String[] args) {
WebClient webClient = WebClient.create("https://jsonplaceholder.typicode.com");
Mono<Post> postMono = webClient.get()
.uri("/posts/1")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx Error: " + response.statusCode());
return Mono.empty();
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx Error: " + response.statusCode());
return Mono.empty();
})
.bodyToMono(Post.class);
postMono.subscribe(
post -> {
System.out.println("Post Title: " + post.getTitle());
},
error -> {
System.err.println("Error: " + error.getMessage());
}
);
try {
Thread.sleep(3000); // Wait for async result
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Conclusion
You can use WebClient
without Spring Boot in a traditional Java project by manually setting up a WebClient
instance and using it asynchronously. The WebClient
API relies on Project Reactor's reactive paradigm, so be sure to manage the event loop correctly, either by using Thread.sleep()
for simple cases or by using Reactor
's schedulers in more complex scenarios.
For a production environment, you would want to integrate this into a larger system with proper exception handling, logging, and asynchronous management to take full advantage of the non-blocking nature of WebClient
.
0 Comments