RestTemplate
RestTemplate is the classic synchronous HTTP client in Spring for calling external REST APIs. It has a simple, template-style API and is still widely seen in existing codebases. This page covers GET and POST calls, error handling, and configuration — but note up front that RestTemplate is in maintenance mode; for new code prefer RestClient or WebClient.
Creating a RestTemplate
Build it through RestTemplateBuilder (auto-configured by Spring Boot) so you get sensible defaults, timeouts, and message converters.
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
@Configuration
public class HttpClientConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.connectTimeout(Duration.ofSeconds(2))
.readTimeout(Duration.ofSeconds(5))
.build();
}
}
Warning: Do not
new RestTemplate()inline in a service. Inject the configured bean so timeouts, interceptors, and converters apply consistently.
GET requests
getForObject returns the deserialized body; getForEntity returns a ResponseEntity so you can inspect status and headers.
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
public record User(Long id, String name, String email) {}
@Service
public class UserClient {
private final RestTemplate rest;
private static final String BASE = "https://api.example.com";
public UserClient(RestTemplate rest) {
this.rest = rest;
}
public User getUser(Long id) {
return rest.getForObject(BASE + "/users/{id}", User.class, id);
}
public ResponseEntity<User> getUserWithMeta(Long id) {
return rest.getForEntity(BASE + "/users/{id}", User.class, id);
}
}
For a generic collection, use exchange with a ParameterizedTypeReference to preserve the element type:
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import java.util.List;
public List<User> listUsers() {
return rest.exchange(
BASE + "/users",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<User>>() {}
).getBody();
}
POST requests
postForObject / postForEntity send a body and read the response. For full control over headers, use exchange with an HttpEntity.
public record CreateUser(String name, String email) {}
public User create(CreateUser body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth("token-123");
HttpEntity<CreateUser> request = new HttpEntity<>(body, headers);
return rest.postForObject(BASE + "/users", request, User.class);
}
Output (deserialized response):
{ "id": 101, "name": "Grace Hopper", "email": "[email protected]" }
Error handling
By default RestTemplate throws on 4xx/5xx: HttpClientErrorException for 4xx and HttpServerErrorException for 5xx (both extend RestClientResponseException). Catch and translate them.
import org.springframework.web.client.*;
public Optional<User> findUser(Long id) {
try {
return Optional.ofNullable(rest.getForObject(BASE + "/users/{id}", User.class, id));
} catch (HttpClientErrorException.NotFound e) {
return Optional.empty(); // 404 -> empty
} catch (HttpStatusCodeException e) {
throw new UpstreamException(
"Upstream failed: " + e.getStatusCode(), e);
} catch (ResourceAccessException e) {
throw new UpstreamException("Upstream timed out or unreachable", e);
}
}
For app-wide behavior, register a custom ResponseErrorHandler on the builder.
return builder
.errorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse res) throws IOException {
// custom mapping of upstream errors
super.handleError(res);
}
})
.build();
Common methods
| Method | Purpose | Returns |
|---|---|---|
getForObject | GET, body only | T |
getForEntity | GET, body + status/headers | ResponseEntity<T> |
postForObject | POST, body only | T |
postForEntity | POST, body + status/headers | ResponseEntity<T> |
put | PUT, no return | void |
delete | DELETE | void |
exchange | Any verb, full control | ResponseEntity<T> |
Why prefer RestClient / WebClient
Note: As of Spring Framework 6.1,
RestTemplateis in maintenance mode — it still works and is not deprecated, but no new features are planned. New code should use the fluent, synchronousRestClient(same blocking model, modern API) or the reactiveWebClient. Both are covered in WebClient & RestClient.
A quick before/after:
// RestTemplate
User u = rest.getForObject(BASE + "/users/{id}", User.class, id);
// RestClient (recommended for new synchronous code)
User u = restClient.get().uri("/users/{id}", id).retrieve().body(User.class);
Pitfalls
- A bare
new RestTemplate()has effectively no timeouts — a slow upstream can hang your threads indefinitely. - Forgetting
ParameterizedTypeReferencefor collections yields aList<LinkedHashMap>instead of typed objects. RestTemplateis blocking; do not call it from reactive (WebFlux) handlers.