When building microservices or applications that rely on external systems, you often need to call third-party REST APIs. While RestTemplate has been widely used for this, WebClient is the modern and more powerful alternative introduced in Spring 5 as part of the reactive web stack.
In this post, youβll learn how to consume external APIs in Spring Boot using WebClient β with complete examples and best practices.

π What is WebClient?
WebClient is a non-blocking, reactive HTTP client. It supports both synchronous and asynchronous calls and is part of the spring-webflux module.
βοΈ Add WebClient Dependency
If you are using Spring Boot 2.4+, WebClient comes bundled with spring-boot-starter-webflux.
Maven:
org.springframework.boot
spring-boot-starter-webflux
π§ Configure WebClient Bean (Optional)
You can define a WebClient bean for reuse across services.
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.baseUrl("https://jsonplaceholder.typicode.com") // default base URL
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
π‘ Making a GET Request
Letβs create a simple service to fetch a list of users from a public API.
β Example: Fetch Users (GET)
@Service
public class UserService {
private final WebClient webClient;
public UserService(WebClient webClient) {
this.webClient = webClient;
}
public List getUsers() {
return webClient.get()
.uri("/users")
.retrieve()
.bodyToFlux(User.class)
.collectList()
.block(); // blocking for simplicity
}
}
π¦ User DTO
@Data
public class User {
private Long id;
private String name;
private String email;
}
π§Ύ Making a POST Request
To send data using POST:
β Example: Create a User
public User createUser(User user) {
return webClient.post()
.uri("/users")
.bodyValue(user)
.retrieve()
.bodyToMono(User.class)
.block();
}
π¨ Handling Errors Gracefully
Error handling is crucial when calling External APIs in Spring Boot.
public Mono getUserById(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
return Mono.error(new RuntimeException("Client error: " + response.statusCode()));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
return Mono.error(new RuntimeException("Server error: " + response.statusCode()));
})
.bodyToMono(User.class);
}
π Non-Blocking Asynchronous Calls
WebClient supports reactive programming with Mono/Flux for async operations.
public Mono getUserAsync(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(User.class);
}
Use .subscribe() or reactive chains to work with this non-blocking result.
π§ͺ Unit Testing WebClient
Use WebClient.Builder with a MockWebServer or mock the WebClient directly.
Example with Mockito:
@Test
void testGetUser() {
WebClient webClient = WebClient.create("http://mock-api");
// inject this mock WebClient in service and test responses
}
π Complete Controller Example
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity> getAllUsers() {
return ResponseEntity.ok(userService.getUsers());
}
@PostMapping
public ResponseEntity createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
}
π§ Best Practices
- Prefer non-blocking (
Mono/Flux) in reactive applications. - Use
WebClient.Builderfor customization and reusability. - Implement retry logic with
.retry()for transient failures. - Use timeout with
.timeout(Duration.ofSeconds(5))for stability. - Avoid
.block()in production unless absolutely necessary.
β Conclusion
WebClient is the go-to HTTP client for modern Spring Boot applications. It is lightweight, non-blocking, and powerful β making it suitable for both synchronous and reactive use cases. With proper error handling and design, you can safely and efficiently integrate with external APIs.