2026년 7월 15일에 작성되었고, 블로그를 이전하며 옮겨온 포스트입니다.
여기에서 원본을 확인할 수 있습니다.
들어가며
얼마 전에 Spring Boot 프로젝트에서 외부 API를 RestTemplate 방식으로 쓰려고 찾아보다가 Spring 공식 문서에서 아래 내용을 발견했다.

As of Spring Framework 7.0, RestTemplate is deprecated in favor of RestClient and will be removed in a future version, please use the "Migrating to RestClient" guide. For asynchronous and streaming scenarios, consider the reactive WebClient.
→ Spring Framework 7.0부터 'RestTemplate'은 'RestClient'로 대체되어 더 이상 권장되지 않으며(deprecated) 향후 버전에서 제거될 예정입니다. 'Migrating to RestClient' 가이드를 참고하여 전환해 주시기 바랍니다. 비동기 및 스트리밍 시나리오의 경우에는 리액티브(reactive) 방식의 'WebClient' 사용을 고려하십시오.
엥? RestClient? 지금까지 RestTemplate만을 사용해 왔었는데, 처음 들어보는 것이었다. 그래서 RestClient, WebClient, RestTemplate이 어떤 차이점을 가졌는지 알아보고 싶었다. 먼저, 카카오 소셜 로그인에서 인가 코드로 액세스 토큰을 받는 API를 3가지 방식으로 코드를 작성해보며 비교해보았다.
카카오 소셜 로그인을 구현하며 코드 비교해보기
카카오 액세스 토큰 API 확인
https://kauth.kakao.com/oauth/token여기로 POST 요청을 보내고, 본문에 필요한 정보들을 넣어줬다.


RestTemplate
먼저 전통적이고 나에게 가장 익숙한 RestTemplate 방식이다. 코드가 생각보다 길고, MultiValueMap과 HttpEntity 설정이 번거롭다.
@PostMapping("/login/kakao")
public KakaoTokenResponse kakaoLogin(@RequestParam String code) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoClientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
ResponseEntity<KakaoTokenResponse> response = restTemplate.exchange(
kakaoUrl, HttpMethod.POST, entity, KakaoTokenResponse.class);
return response.getBody();
}
RestClient
그 다음으로 Spring에서 권장하고 있는 방식이다. 그냥.. 대충봐도.. RestTemplate 방식보다는 압도적으로 코드가 간결하다.
코드를 작성하면서 느꼈는데, 그냥 RestClient 쓰기로 했다. 너무 간결하고 직관적이다.
@PostMapping("/login/kakao")
public KakaoTokenResponse kakaoLoginWithRestClient(@RequestParam String code) {
return RestClient.builder().build()
.post()
.uri(kakaoUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body("grant_type=authorization_code" +
"&client_id=" + kakaoClientId +
"&redirect_uri=" + redirectUri +
"&code=" + code)
.retrieve()
.body(KakaoTokenResponse.class);
}
WebClient
이제 마지막으로 Reactive 프로그래밍과 비동기 처리를 지원하는 WebClient 방식이다. RestClient와 매우 유사한 빌더 패턴 구조를 가지고 있지만, 반환 타입이 Mono로 감싸져 있다는 점이 가장 큰 특징이다.
@PostMapping("/login/kakao")
public Mono<KakaoTokenResponse> kakaoLoginWithWebClient(@RequestParam String code) {
return WebClient.builder().build()
.post()
.uri(kakaoUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue("grant_type=authorization_code" +
"&client_id=" + kakaoClientId +
"&redirect_uri=" + redirectUri +
"&code=" + code)
.retrieve()
.bodyToMono(KakaoTokenResponse.class);
}
여기서 그럼 Mono는 뭘까?
Mono는 Reactive Streams를 구현한 Project Reactor의 핵심 객체인 비동기 컨테이너이다.
- kakaoLoginWithWebClient 가 호출되는 즉시 카카오 서버에서 응답이 오는 것은 아니다. 즉, Mono는 실제 데이터가 아닌, “조금 있다가 카카오가 응답 보내주면 여기 담아줄게”라고 약속하는 것과 같다.
- 또, 카카오 서버로의 응답을 기다리는 동안 Tomact의 Thread가 block되지 않고 다른 사용자들의 요청을 처리할 수 있도록 시스템 자원을 효율적으로 사용하게 해준다.
그렇다면, 카카오 로그인에서는 어떤 방식이 좋을까?
1. 로그인 프로세스는 동기적 흐름이 자연스럽다.
카카오 로그인은 클라이언트가 인가 코드를 전달하면 백엔드는 인가 코드를 가지고 액세스 토큰을 받아오고, 그 토큰으로 카카오 프로필 정보를 조회하여 우리 서비스의 JWT를 발급하는 순차적인 과정을 거친다. 그렇기 때문에 비동기 처리에 큰 메리트가 없다.
2. 굳이 필요없는 WebFlux 의존성
원래 MVC 환경에서 WebClient를 사용하려면, HTTP 클라이언트 하나 때문에 Reactive 전체가 담긴 org.springframework.boot:spring-boot-starter-webflux 의존성을 통째로 들고 와야했지만, https://spring.io/blog/2025/09/30/the-state-of-http-clients-in-spring 이 글에서 확인할 수 있듯이, Spring Boot 4.0부터는 클라이언트 의존성이 완전히 쪼개져 독립적인 전용 스타터가 제공된다.

With Spring Boot 4.0, applications can now use the “org.springframework.boot:spring-boot-starter-webclient” or “org.springframework.boot:spring-boot-starter-restclient” to express the need for an HTTP client.
→ Spring Boot 4.0부터는 애플리케이션에서 HTTP 클라이언트가 필요함을 나타내기 위해 "org.springframework.boot:spring-boot-starter-webclient" 또는 "org.springframework.boot:spring-boot-starter-restclient"를 사용할 수 있습니다.
즉, 이제는 무거운 WebFlux 의존성 걱정 없이 딱 필요한 전용 스타터(spring-boot-starter-restclient)만 추가하여 가볍고 깔끔하게 동기식 빌더 패턴을 누릴 수 있게 된 것이다.
그러면, RestClient와 RestTemplate의 예외 처리 방식은 어떻게 다를까?
이제 궁금해진 것이 ”RestClient와 RestTemplate의 예외 처리는 어떻게 다를까?”이다. 그래서 Spring 공식 문서를 참고해서 예외 처리 방법에 대해 알아보았다.
RestTemplate에서의 예외 처리
@PostMapping("/login/kakao")
public KakaoTokenResponse kakaoLogin(@RequestParam String code) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoClientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
try {
ResponseEntity<KakaoTokenResponse> response = restTemplate.exchange(
kakaoUrl, HttpMethod.POST, entity, KakaoTokenResponse.class);
return response.getBody();
} catch (HttpClientErrorException e) {
// 4xx 클라이언트 에러 예외 처리
throw new RuntimeException("카카오 인증 요청 정보가 잘못되었습니다.");
} catch (HttpServerErrorException e) {
// 5xx 서버 에러 예외 처리
throw new RuntimeException("카카오 서버 장애가 발생했습니다.");
}
}
RestTemplate에서는 HTTP 에러가 발생하면 기본적으로 내부 예외를 던져버리는데, 특정 상태 코드 별로 커스텀 예외를 던지려면, 호출부 전체를 try-catch 문을 이용해 감싸서 풀어내야했다.
RestClient에서의 예외 처리
@PostMapping("/login/kakao")
public KakaoTokenResponse kakaoLoginWithRestClient(@RequestParam String code) {
return RestClient.builder().build()
.post()
.uri(kakaoUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body("grant_type=authorization_code" +
"&client_id=" + kakaoClientId +
"&redirect_uri=" + redirectUri +
"&code=" + code)
.retrieve()
// 4xx 클라이언트 에러 예외 처리
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new RuntimeException("카카오 인증 요청 정보가 잘못되었습니다.");
})
// 5xx 서버 에러 예외 처리
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new RuntimeException("카카오 서버 장애가 발생했습니다.");
})
.body(KakaoTokenResponse.class);
}
하지만, RestClient에서는 별도의 try-catch 문 없이 빌더 체이닝 내부에 .onStatus() 메서드를 사용하여 예외를 처리할 수 있고 예외 처리 로직이 파편화되지 않는다. 또한, 코드 호출 지점에 모이기 때문에 코드의 가독성이 압도적으로 좋아지는 것을 볼 수 있다.
REST Client 3가지 비교하기

마무리
이번에 RestClient, WebClient를 알아보면서 RestTemplate이 편했던 것은 그냥 “오랫동안 썼으니까”라는 이유이지 더 이상 최선의 선택이 아니라는 것을 알게되었다. Spring 7.0부터 RestTemplate의 Deprecated가 예고된 이상, 지금부터라도 RestClient로 옮겨가는 것이 맞는 방향이라고 생각했다. 그렇기 때문에 지금부터는 특별히 비동기 처리가 필요한 상황이 아니라면 RestClient를 기본으로 가져가기로 다짐했다. 또한, 앞으로도 이러한 부분들이 있다면 최대한 알아보고 최신의 트렌드를 알아가기 위해 노력해야겠다는 생각을 했다.
'Backend > Java' 카테고리의 다른 글
| 서비스에 AI 붙이기: Spring AI + Ollama + Function Calling (0) | 2026.08.23 |
|---|---|
| (Spring Boot) Elasticsearch를 활용해 1,000만 개 데이터에서의 검색 속도 개선해보기 (0) | 2026.08.23 |
| AWS 탈탈 털리고, Amazon Lightsail을 이용해 안전하게 파일 업로드 해보기 (Spring Boot) (0) | 2026.08.23 |
| Java Spring Boot에서 템플릿을 설정하고 이메일 발송하기 (0) | 2026.08.23 |
| 값이 반드시 유효해야 할 때: Spring Boot에서 Validation 처리하기 (0) | 2026.08.22 |