Skip to content
Spring Boot sb web 3 min read

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

MethodPurposeReturns
getForObjectGET, body onlyT
getForEntityGET, body + status/headersResponseEntity<T>
postForObjectPOST, body onlyT
postForEntityPOST, body + status/headersResponseEntity<T>
putPUT, no returnvoid
deleteDELETEvoid
exchangeAny verb, full controlResponseEntity<T>

Why prefer RestClient / WebClient

Note: As of Spring Framework 6.1, RestTemplate is in maintenance mode — it still works and is not deprecated, but no new features are planned. New code should use the fluent, synchronous RestClient (same blocking model, modern API) or the reactive WebClient. 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 ParameterizedTypeReference for collections yields a List<LinkedHashMap> instead of typed objects.
  • RestTemplate is blocking; do not call it from reactive (WebFlux) handlers.
Last updated June 13, 2026
Was this helpful?