Microservices and integrations constantly call external HTTP APIs.
Spring Framework 6.1 introduced RestClient, a fluent, synchronous HTTP client that replaces RestTemplate for new code (RestTemplate still works but is in maintenance mode). Spring Boot auto-configures a RestClient.Builder bean with the application's JSON message converters; inject the builder, set a base URL and defaults, and build a client:
RestClient client = builder.baseUrl("https://api.example.com").build();A call reads like the HTTP request: client.get().uri("/users/{id}", id).retrieve().body(User.class). Use toEntity to get status and headers too, body(new ParameterizedTypeReference<List<User>>() {}) for generic types, and .post().body(dto) to send JSON.
By default a 4xx or 5xx response throws RestClientResponseException (HttpClientErrorException/HttpServerErrorException). Customise with onStatus(HttpStatusCode::is4xxClientError, (req, res) -> ...).
HTTP interfaces are a declarative alternative: annotate a Java interface with @GetExchange("/users/{id}") methods, then create an implementation with HttpServiceProxyFactory backed by a RestClient. The interface reads like a contract and is easy to mock in tests.
Always configure timeouts (connect and read) through the request factory, for example ClientHttpRequestFactorySettings or the spring.http.client.* properties in recent Boot versions; an unbounded call to a slow service ties up threads. For reactive apps use WebClient instead.
Example
record User(long id, String name) {}
@Service
class UserClient {
private final RestClient client;
UserClient(RestClient.Builder builder) { // auto-configured by Boot
this.client = builder.baseUrl("https://jsonplaceholder.typicode.com").build();
}
User find(long id) {
return client.get().uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (req, res) -> {
throw new IllegalArgumentException("no user " + id);
})
.body(User.class);
}
List<User> all() {
return client.get().uri("/users").retrieve()
.body(new ParameterizedTypeReference<List<User>>() {});
}
}
// find(1) -> User[id=1, name=Leanne Graham]Remember
- RestClient is the modern synchronous client; RestTemplate is in maintenance
- Inject Boot's
RestClient.Builderto inherit JSON configuration - 4xx/5xx throw by default; customise with
onStatus - Always set connect and read timeouts
Watch out for
- Creating
RestClient.create()everywhere, losing Boot's configured converters, observability and timeouts.