Spring에서 외부 API를 호출할 때 사용되는 방법은 RestTemplate, WebClient, RestClient, OpenFeign 등이 있습니다. 이번 글에서는 이 중 RestTemplate에 대한 공부한 내용을 정리하고자 합니다.
비록 Spring 공식 문서에서는 WebClient 사용을 지향하지만, 여전히 많은 레거시 시스템과 프로젝트의 표준으로 자리 잡고 있는 RestTemplate에 대해 다뤄보고자 합니다.
RestTemplate란❓
RestTemplate은 Spring 3.0부터 제공된 동기식 HTTP 클라이언트입니다.
즉, HTTP 통신을 위한 도구로 RESTful API 웹 서비스와의 상호작용을 쉽게 외부 도메인에서 데이터를 가져오거나 전송할 때 사용되는 스프링 프레임워크의 클래스를 의미합니다.
RestTemplate이 Deprecated 된다고 알고 있는 사람이 종종있는데 아직까지도 deprecated가 아닙니다. 새로운 기능 추가는 없지만 버그 수정과 보안 업데이트는 계속되고있습니다.
RestTemplate 주요 특징
- 블로킹 기반의 동기(Synchronous) 방식으로 동작합니다.
- HTTP 메서드별로 직관적인 메서드를 제공합니다.
- 자동으로 JSON/XML과 객체 간 변환을 처리합니다.
블로킹 요청(Blocking Request) 란❓
- 하나의 작업이 진행되는 동안 그 작업이 끝날 때까지 제어권을 붙잡아 두어
다음 작업이 실행되지 못하게 막는 상태를 의미합니다. - unblocked 상태가 되면 다음 요청에 대해 처리를 수행합니다.
즉, 요청을 보낸 스레드가 응답이 올 때까지 "멈춰서 기다리는" 요청 방식
RestTemplate의 역할

RestTemplate은 Spring Framework와 외부 REST API 사이의 중간 계층 역할을 합니다.
- Request a resource
- Spring 애플리케이션이 RestTemplate에게 외부 API 호출을 요청합니다
- RestTemplate 처리
- RestTemplate이 HTTP 요청을 구성하고 실행합니다
- REST API 호출
- 실제 외부 REST API 서버에 HTTP 요청을 보냅니다.
- Get a resource
- 응답을 받아서 자바 객체로 변환하여 애플리케이션에 반환합니다.
- 자바 객체로 변환할 때 RestTemplate는 내부적으로 HttpMessageConverter를 사용해서 HTTP응답 (JSON, XML 등)을 자바 객체로 자동 변환 합니다.
- 이러한 중간 계층 덕분에 개발자는 복잡한 HTTP 통신 로직을 직접 작성하지 않고, 간단한 메서드 호출만으로 외부 API와 통신할 수 있습니다.
- 응답을 받아서 자바 객체로 변환하여 애플리케이션에 반환합니다.
RestTemplate 설정하기
1. 의존성 추가
- Spring Boot Web 스타터를 사용하면 별도의 의존성 추가 없이 RestTemplate을 사용할 수 있습니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
2. Bean 등록
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
}
- RestTemplate은 여러 곳에서 재사용하는 것이 일반적이므로, Spring Bean으로 등록하여 사용하는 것을 권장합니다.
3. RestTemplateBuilder 활용
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.rootUri("https://api.example.com") // 기본 URL 설정
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.defaultHeader("User-Agent", "MyApp/1.0")
.interceptors(new LoggingInterceptor()) // 인터셉터 추가
.errorHandler(new CustomErrorHandler()) // 에러 핸들러 설정
.build();
}
}
- RestTemplateBuilder를 사용하면 더 세밀한 설정이 가능합니다.
- Spring Boot는 RestTemplateBuilder를 자동으로 제공합니다.
- 이를 주입받아 RestTemplate을 생성하면, Spring Boot의 자동 설정 혜택을 받을 수 있습니다.
- 위 예제에서는 연결 타임아웃과 읽기 타임아웃을 각각 5초로 설정했습니다.
- rootUri
- 모든 요청에 공통으로 사용될 기본 URL을 설정합니다.
- 이후 요청 시 상대 경로만 지정하면 됩니다
- setConnectTimeout
- 서버와의 연결을 시도하는 최대 시간입니다.
- 이 시간 내에 연결되지 않으면 예외가 발생합니다
- setReadTimeout
- 서버로부터 데이터를 읽는 최대 시간입니다.
- 연결은 되었지만 응답이 느릴 때 적용됩니다
- defaultHeader
- 모든 요청에 기본으로 포함될 HTTP 헤더를 설정합니다
- interceptors
- 요청/응답을 가로채서 로깅, 인증 토큰 추가 등의 공통 로직을 처리합니다
- errorHandler
- HTTP 에러 응답(4xx, 5xx)을 커스텀하게 처리하는 핸들러를 등록합니다
여러개의 RestTemplate Bean 설정하기
서로 다른 API 서버를 호출해야 하는 경우, 여러 개의 RestTemplate Bean을 등록할 수 있습니다.
각 API 서버마다 다른 설정(타임아웃, 기본 URL, 헤더 등)이 필요할 때 유용합니다.
@Configuration
public class RestTemplateConfig {
@Bean
@Qualifier("paymentRestTemplate")
public RestTemplate paymentRestTemplate(RestTemplateBuilder builder) {
return builder
.rootUri("https://payment-api.example.com")
.setConnectTimeout(Duration.ofSeconds(3))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
@Bean
@Qualifier("userRestTemplate")
public RestTemplate userRestTemplate(RestTemplateBuilder builder) {
return builder
.rootUri("https://user-api.example.com")
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
}
@Qualifier
- Bean의 식별자를 지정하는 어노테이션입니다.
- 같은 타입(RestTemplate)의 Bean이 여러 개 있을 때, 각각을 구분하기 위해 사용합니다.
@Qualifier 없이같은 타입의 Bean이 여러 개 있으면, Spring은 어떤 Bean을 주입해야 할지 알 수 없어 NoUniqueBeanDefinitionException 에러가 발생합니다.
@Service
@RequiredArgsConstructor
public class PaymentService {
@Qualifier("paymentRestTemplate")
private final RestTemplate restTemplate;
// ...
}
@Qualifier("paymentRestTemplate")
필드 위에 붙이는 어노테이션으로, 어떤 Bean을 주입받을지 명시합니다.
- 동작 원리
- Spring이 PaymentService의 인스턴스를 생성할 때, RestTemplate 타입의 Bean 중에서 @Qualifier("paymentRestTemplate")로 지정된 Bean을 찾아서 주입합니다.
- 위치
- @Qualifier는 private final RestTemplate restTemplate; 바로 위에 위치해야 합니다.
- 이름 일치
- Config 클래스에서 지정한 Qualifier 이름("paymentRestTemplate")과 정확히 일치해야 합니다.
RestTemplate 주요 메서드 활용하기
| Method | HTTP Method | Return Type | 설명 |
| getForObject() | GET | Object | GET 요청에 대한 결과를 객체로 반환합니다 |
| getForEntity() | GET | ResponseEntity | GET 요청에 대한 결과를 ResponseEntity로 반환합니다 |
| postForLocation() | POST | URI | POST 요청에 대한 결과로 헤더에 저장된 URI 반환합니다 |
| postForObject() | POST | Object | POST 요청에 대한 결과를 객체로 반환합니다 |
| postForEntity() | POST | ResponseEntity | POST 요청에 대한 결과를 ResponseEntity로 반환합니다 |
| put() | PUT | void | PUT 요청을 실행합니다 |
| patchForObject() | PATCH | Object | PATCH 요청을 실행하고 결과를 객체로 반환합니다 |
| delete() | DELETE | void | DELETE 요청을 실행합니다 |
| headForHeaders() | HEADER | HttpHeaders | 헤더 정보를 추출하고 HTTP HEAD 메서드를 사용합니다 |
| optionsForAllow() | OPTIONS | Set<HttpMethod> | 지원되는 HTTP 메서드를 추출합니다 |
| exchange() | any | ResponseEntity | 헤더를 생성하고 모든 요청 방법을 허용합니다 |
| execute() | any | T | 요청/응답 콜백을 수정합니다 |
1. GET 요청
getForObject()
- 응답 본문을 직접 객체로 반환합니다.
@Service
@RequiredArgsConstructor
public class UserService {
private final RestTemplate restTemplate;
public User getUser(Long id) {
String url = "https://api.example.com/users/" + id;
return restTemplate.getForObject(url, User.class);
}
// URI Variables 사용
public User getUserWithVariable(Long id) {
String url = "https://api.example.com/users/{id}";
return restTemplate.getForObject(url, User.class, id);
}
}
getForEntity()
- 응답 본문뿐만 아니라 HTTP 상태 코드, 헤더 등 전체 응답 정보를 포함하는 ResponseEntity를 반환합니다.
public ResponseEntity<User> getUserEntity(Long id) {
String url = "https://api.example.com/users/{id}";
ResponseEntity<User> response = restTemplate.getForEntity(url, User.class, id);
// 상태 코드 확인
HttpStatus statusCode = response.getStatusCode();
// 헤더 정보 확인
HttpHeaders headers = response.getHeaders();
// 응답 본문
User user = response.getBody();
return response;
}
2. POST 요청
postForObject()
- POST 요청을 보내고 응답 본문을 객체로 반환합니다.
public User createUser(User newUser) {
String url = "https://api.example.com/users";
return restTemplate.postForObject(url, newUser, User.class);
}
postForEntity()
- POST 요청을 보내고 ResponseEntity를 반환합니다.
public ResponseEntity<User> createUserEntity(User newUser) {
String url = "https://api.example.com/users";
ResponseEntity<User> response = restTemplate.postForEntity(url, newUser, User.class);
// 생성된 리소스의 Location 헤더 확인
URI location = response.getHeaders().getLocation();
return response;
}
postForLocation()
- POST 요청 후 생성된 리소스의 Location URI를 반환합니다.
public URI createUserLocation(User newUser) {
String url = "https://api.example.com/users";
return restTemplate.postForLocation(url, newUser);
}
3. PUT 요청
put()
- PUT 요청을 보냅니다.
반환값이 없습니다(void).
public void updateUser(Long id, User updatedUser) {
String url = "https://api.example.com/users/{id}";
restTemplate.put(url, updatedUser, id);
}
4. DELETE 요청
delete()
- DELETE 요청을 보냅니다.
반환값이 없습니다(void).
public void deleteUser(Long id) {
String url = "https://api.example.com/users/{id}";
restTemplate.delete(url, id);
}
5. 쿼리 파라미터 처리
UriComponentsBuilder 사용
public List<User> searchUsers(String name, Integer age) {
String baseUrl = "https://api.example.com/users/search";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.queryParam("name", name)
.queryParam("age", age);
String url = builder.toUriString();
return restTemplate.getForObject(url, List.class);
}
Map으로 쿼리 파라미터 전달
public List<User> searchUsersWithMap(String name, Integer age) {
String url = "https://api.example.com/users/search?name={name}&age={age}";
Map<String, Object> params = new HashMap<>();
params.put("name", name);
params.put("age", age);
return restTemplate.getForObject(url, List.class, params);
}
Connection Pool 설정하기
RestTemplate은 기본적으로 요청마다 새로운 연결(Socket)을 생성하고 닫는 방식을 사용합니다.
하지만 트래픽이 많은 서비스에서는 이 과정이 오버헤드가 되어 성능 저하를 일으킬 수 있습니다. 이때 커넥션 풀(Connection Pool)을 사용하면 미리 만들어 놓은 연결을 재사용하여 효율을 극대화할 수 있습니다.
1. 의존성 추가
커넥션 풀 기능을 제공하는 Apache HttpClient 라이브러리를 build.gradle에 추가해야 합니다.
dependencies {
// RestTemplate의 커넥션 풀 관리를 위한 라이브러리
implementation 'org.apache.httpcomponents.client5:httpclient5'
}
2. RestTemplateConfig에 적용하기
RestTemplateBuilder를 사용하여 커넥션 풀이 적용된 HttpComponentsClientHttpRequestFactory를 설정합니다.
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
// 1. 커넥션 풀 설정 적용
.requestFactory(this::clientHttpRequestFactory)
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
// Apache HttpClient 5 설정
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100); // 최대 전체 커넥션 수
connectionManager.setDefaultMaxPerRoute(20); // IP:Port 하나당 유지할 커넥션 수
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
}
성능 최적화를 위해 Apache HttpClient 5 기반의 커넥션 풀을 적용한 설정입니다.
- setMaxTotal
- 애플리케이션 전체에서 유지할 최대 커넥션 수를 100개로 제한하여 리소스 고갈을 방지합니다.
- setDefaultMaxPerRoute
- 특정 호스트(예: API 서버 A)당 최대 커넥션을 20개로 설정해 특정 API 지연이 전체 시스템으로 전이되는 것을 막습니다.
- requestFactory
- RestTemplateBuilder에 이 설정을 주입함으로써, 매 요청마다
소켓을 새로 여는대신 풀에 있는 커넥션을 재사용하게 됩니다."
- RestTemplateBuilder에 이 설정을 주입함으로써, 매 요청마다
'Framework > Spring\Spring boot' 카테고리의 다른 글
| [Spring boot] 우리가 보낸 객체는 어떻게 서버까지 흘러갈까? (직렬화와 역직렬화) (0) | 2026.01.13 |
|---|---|
| [Springboot] Spring Boot에서 Service와 ServiceImpl 분리, 꼭 필요할까❓ (0) | 2025.02.28 |
| [SpringBoot] ObjectMapper와 직렬화/역직렬화 (0) | 2025.02.27 |
| [Spring boot] DTO와 Entity 변환 위치에 대한 고찰 - Controller vs Service Layer (0) | 2024.12.25 |
| [Spring boot] Cache Manager와 @Cacheable 어노테이션 이해하기 (0) | 2024.09.02 |