LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2919 min read

Consuming External APIs

Call external services from a Spring Boot application using RestTemplate and the modern WebClient, including error handling and DTO mapping.

Introduction

Most real applications do not exist in isolation - they call payment gateways, weather services, other internal microservices, or third-party APIs. Spring Boot offers two main HTTP client options for this: the older, simpler RestTemplate, and the modern, non-blocking WebClient. In this lesson you will call an external API with both, map the response into your own DTOs, and handle failures gracefully.

What You Will Learn
  • The difference between RestTemplate and WebClient.
  • How to make a GET request with RestTemplate.
  • How to make the same call with WebClient.
  • How to map a JSON response into your own DTO class.
  • How to handle errors from an external service gracefully.

RestTemplate vs WebClient

RestTemplate has been part of Spring for years and is simple to use, but it is blocking and is now in maintenance mode - the Spring team recommends WebClient for new code. WebClient is part of Spring WebFlux, supports both blocking and non-blocking usage, and is the forward-looking choice even in a traditional (non-reactive) Spring Boot application.

AspectRestTemplateWebClient
StyleBlocking, synchronousNon-blocking, reactive (usable synchronously too)
StatusMaintenance mode, not recommended for new codeActively developed, recommended default
DependencyIncluded with Spring WebRequires spring-boot-starter-webflux

Calling an API with RestTemplate

RestTemplate is registered as a bean and then used to make GET, POST, and other HTTP calls with a small, direct API.

@Configuration
public class RestClientConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.connectTimeout(Duration.ofSeconds(3))
.readTimeout(Duration.ofSeconds(5))
.build();
}
}
@Service
public class WeatherClient {
private final RestTemplate restTemplate;
public WeatherClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public WeatherResponse getWeather(String city) {
String url = "https://api.example.com/weather?city={city}";
return restTemplate.getForObject(url, WeatherResponse.class, city);
}
}
Result

Click Run to see what this code prints.

Calling an API with WebClient

WebClient is built fluently and, even in a traditional servlet application, can be used synchronously by calling .block() on the result.

@Configuration
public class WebClientConfig {
@Bean
public WebClient weatherWebClient(WebClient.Builder builder) {
return builder.baseUrl("https://api.example.com").build();
}
}
@Service
public class WeatherClient {
private final WebClient webClient;
public WeatherClient(WebClient weatherWebClient) {
this.webClient = weatherWebClient;
}
public WeatherResponse getWeather(String city) {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/weather").queryParam("city", city).build())
.retrieve()
.bodyToMono(WeatherResponse.class)
.block();
}
}
Result

Click Run to see what this code prints.

Mapping the Response to a DTO

Both clients deserialize the JSON response directly into a Java type using Jackson, the same library Spring uses for its own REST controllers. A record works well as a lightweight response DTO.

public record WeatherResponse(String city, double tempCelsius, String condition) {}
Never Return the External API's Response Directly

Map the external response into your own DTO type instead of passing it straight through from your controller. This insulates your API from the third party's field names changing, and lets you shape the response to fit your own contract.

Handling Errors

External services fail - they time out, return 500s, or go down entirely. Wrap calls so a failure does not crash your own request and, ideally, degrades gracefully.

public WeatherResponse getWeather(String city) {
try {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/weather").queryParam("city", city).build())
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError,
response -> Mono.error(new WeatherServiceException("Invalid request to weather API")))
.onStatus(HttpStatusCode::is5xxServerError,
response -> Mono.error(new WeatherServiceException("Weather API is unavailable")))
.bodyToMono(WeatherResponse.class)
.block();
} catch (WebClientRequestException ex) {
throw new WeatherServiceException("Could not reach weather API", ex);
}
}
Downstream Failure Response

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Calling new RestTemplate() directly instead of injecting a configured bean, which loses timeout and connection pool settings.
  • Never setting a connection or read timeout, letting a slow external service hang a request indefinitely.
  • Passing the raw external response straight back from your own controller instead of mapping it to your own DTO.
  • Not handling 4xx/5xx responses from the external API, letting an unchecked exception bubble up as a generic 500.
  • Using WebClient reactively throughout a call chain that is otherwise entirely blocking, mixing paradigms inconsistently.

Best Practices

  • Prefer WebClient for new code, even if used synchronously with .block().
  • Always configure explicit connection and read timeouts.
  • Map external responses into your own DTOs rather than exposing them directly.
  • Handle both client and server error status codes from the external API explicitly.
  • Consider a resilience library (like Resilience4j) for retries and circuit breaking around unreliable external calls.

Frequently Asked Questions

Not formally deprecated as of recent Spring versions, but it is officially in maintenance mode - no new features are planned, and the Spring team explicitly recommends WebClient for new development.

No. You can add spring-boot-starter-webflux purely to get the WebClient class and still build a traditional, blocking Spring MVC application - just call .block() where you need a synchronous result.

Use a tool like WireMock or MockWebServer to stand up a fake HTTP server in your test, or mock the client class itself if the test only needs to verify your own logic around the call.

Key Takeaways

  • RestTemplate is simple but in maintenance mode; WebClient is the recommended modern choice.
  • Register HTTP clients as beans so timeouts and other settings are configured consistently.
  • Map external JSON responses into your own DTOs instead of exposing them directly.
  • Handle 4xx and 5xx responses from external services explicitly rather than letting errors propagate unchecked.
  • A slow or failing external service should degrade gracefully, not take your own API down with it.

Summary

Calling external APIs is a routine part of most real Spring Boot applications. Favor WebClient for new code, always configure timeouts, map responses into your own DTOs, and handle failures explicitly so a struggling third-party service never becomes your own outage.

Next Lesson →

Packaging & Running Spring Boot Apps