Hot Posts

hot/hot-posts

Switching RestTemplate to using WebClient



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:

gradle
dependencies { 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.

java
import 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)

java
import 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)

java
import 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 a Mono or Flux, 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:

java
public 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):

java
public class ApiResponse { private String field1; private int field2; // Getters and setters }

Fetching and Mapping JSON to POJO

java
public 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.

java
public 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:

java
import 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:

gradle
dependencies { 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

java
import 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:

java
public class Post { private int id; private String title; private String body; // Getters and setters }

Example of Fetching JSON and Mapping to a POJO:

java
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 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:

java
import 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:

java
import 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.

Post a Comment

0 Comments