Working with External APIs

1. Creating RestTemplate for HTTP Requests

Example: RestTemplate with timeout and base URL

@Bean
RestTemplate restTemplate(RestTemplateBuilder b) {
  return b.connectTimeout(Duration.ofSeconds(2))
          .readTimeout(Duration.ofSeconds(5))
          .rootUri("https://api.example.com")
          .build();
}
// Usage
String json = restTemplate.getForObject("/users/{id}", String.class, 42);
Note: RestTemplate is in maintenance mode. Prefer RestClient (Spring 6.1+) or WebClient.

2. Using WebClient for Reactive HTTP Requests

Example: WebClient for reactive HTTP calls

@Bean
WebClient webClient(WebClient.Builder b) {
  return b.baseUrl("https://api.example.com")
          .defaultHeader(HttpHeaders.USER_AGENT, "myapp/1.0")
          .build();
}
Mono<UserDto> user = webClient.get().uri("/users/{id}", 42)
  .retrieve().bodyToMono(UserDto.class);

3. Using RestClient for Modern HTTP Requests

Example: RestClient for synchronous HTTP calls

@Bean
RestClient restClient(RestClient.Builder b) {
  return b.baseUrl("https://api.example.com").build();
}
UserDto u = restClient.get().uri("/users/{id}", 42)
  .retrieve().body(UserDto.class);

4. Configuring Connection Timeouts

Setting Default
Connect timeout None — set explicitly
Read/response timeout None — set explicitly
Connection pool size Depends on client (Apache HC, Reactor Netty)

5. Handling HTTP Response Codes

Example: Handle 4xx and 5xx with custom exceptions

UserDto u = restClient.get().uri("/users/{id}", 42)
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError,
            (req, res) -> { throw new NotFoundException("user " + 42); })
  .onStatus(HttpStatusCode::is5xxServerError,
            (req, res) -> { throw new UpstreamException(res.getStatusCode().value()); })
  .body(UserDto.class);

6. Using RestTemplate Interceptors

Example: Add bearer auth header via interceptor

ClientHttpRequestInterceptor auth = (req, body, exec) -> {
  req.getHeaders().setBearerAuth(tokenProvider.get());
  return exec.execute(req, body);
};
restTemplate.setInterceptors(List.of(auth));

7. Implementing Retry Logic for Failed Requests

Example: Retry with backoff and recover fallback

@Retryable(retryFor = {ResourceAccessException.class},
           maxAttempts = 3, backoff = @Backoff(delay = 200, multiplier = 2))
public UserDto fetchUser(Long id) {
  return restClient.get().uri("/users/{id}", id).retrieve().body(UserDto.class);
}
@Recover public UserDto fallback(ResourceAccessException ex, Long id) { return UserDto.unknown(id); }

8. Using Circuit Breaker with External APIs

Example: Circuit breaker with fallback method

@CircuitBreaker(name = "userApi", fallbackMethod = "fallback")
public UserDto fetch(Long id) {
  return restClient.get().uri("/users/{id}", id).retrieve().body(UserDto.class);
}
public UserDto fallback(Long id, Throwable t) { return UserDto.unknown(id); }

9. Handling API Authentication

Scheme Header / Method
Bearer Authorization: Bearer <token>
Basic Authorization: Basic base64(user:pass)
API Key X-API-Key: ... or query param
OAuth2 client credentials Spring Security OAuth2 client
mTLS Configure SSLContext on HttpClient

10. Parsing JSON Responses (Jackson)

Example: Deserialize generic list response

List<UserDto> users = restClient.get().uri("/users")
  .retrieve()
  .body(new ParameterizedTypeReference<List<UserDto>>() {});

11. Testing External API Integration (MockRestServiceServer)

Example: Mock REST server in test

@RestClientTest(UserClient.class)
class UserClientTests {
  @Autowired UserClient client;
  @Autowired MockRestServiceServer server;
  @Test void fetch() {
    server.expect(requestTo("https://api.example.com/users/1"))
          .andRespond(withSuccess("{\"id\":1,\"name\":\"Alice\"}", MediaType.APPLICATION_JSON));
    assertEquals("Alice", client.fetch(1L).name());
  }
}

12. Using Feign Client for Declarative REST Clients

Example: Feign declarative HTTP client

@FeignClient(name = "users", url = "https://api.example.com")
public interface UserApi {
  @GetMapping("/users/{id}") UserDto get(@PathVariable Long id);
  @PostMapping("/users") UserDto create(@RequestBody NewUser body);
}
// @EnableFeignClients on @SpringBootApplication