Introduce
Spring Boot 애플리케이션에서 외부 API를 호출하는 일은 매우 흔하다.
Spring 6부터는 기존 RestTemplate을 대체하는 새로운 HTTP 클라이언트로 RestClient가 도입되었고, 비교적 단순한 API 덕분에 빠르게 적용할 수 있다는 장점이 있다.
하지만 실제로 RestClient를 사용하다 보면 이런 궁금증이 생긴다.
- RestClient.create()는 내부적으로 무엇을 만들까?
- builder() 방식과는 어떤 차이가 있을까?
- 매번 새로 만들어도 괜찮을까, 아니면 재사용해야 할까?
이 글에서는 RestClient가 생성되는 방식과 내부 동작 구조를 직접 뜯어보며 살펴본다.
RestClient Construction
RestClient는 두 가지 API로 생성할 수 있다. 둘 다 내부적으로는 DefaultRestClientBuilder를 사용하지만, 커스터마이징이 필요한지 여부에 따라 선택 기준이 갈린다.
📌 create - 기본값 빠른 생성

겉보기에는 RestClient.create() 같은 정적 메서드로 바로 생성되는 것처럼 보이지만, 실제로 RestClient는 빌더를 통해서만 생성되는 객체다. create() 역시 내부적으로는 다음과 같은 흐름을 가진다.
- DefaultRestClientBuilder 생성
- 기본 설정 주입
- build() 호출
- 최종 RestClient 반환
즉, RestClient 생성의 중심에는 항상 Builder가 존재한다.
📌 builder() - 커스터마이징 가능

DefaultRestClientBuilder는 RestClient를 구성하기 위한 모든 설정을 필드로 보관한다. 대표적으로 다음과 같은 요소들이 있다.
- ClientHttpRequestFactory
- HttpMessageConverter
- Header
- Interceptor
- baseUrl
이 시점에서는 아직 HTTP 호출을 할 수 있는 객체가 아니다. 단지 “어떤 설정으로 RestClient를 만들 것인가”에 대한 정의만 존재한다.
Internal Configuration
📌 DefaultRestClientBuilder

build()는 요청을 보낼 때 필요한 기본 구성 요소들을 한 번에 조립하고 있다.
ClientHttprequestFactory 로 실제 HTTP 전송 엔진을 결정하고, UriBuilderFactory 로 baseUrl, 템플릿 기반의 최종 URI를 생성하며 HttpMessageConverter 를 통해 바디의 직렬화와 역직렬화를 처리한다.
📌 Request Factory
HttpMessageConverter 는 스프링을 사용하다 보면 필연적으로 접하는 클래스라 어떤 역할을 하는지 알고 있고, UriBuilderFactory 는 최종 URI를 생성한다는 것이 직관적으로 잘 이해가 된다.
하지만 ClientHttprequestFactory 로 실제 HTTP 전송 엔진을 결정한다는 것이 잘 이해가 되지 않는다.
결정한다라는 말의 의미는 “여러가지의 case 중 1가지를 선택한다” 와 같은 의미이다.
그렇다면 ClientHttprequestFactory 로 결정할 수 있는 전송 엔진은 어떤 것이 있고, 어떤 식으로 결정될까?
REST Clients :: Spring Framework
You can define an HTTP Service as a Java interface with @HttpExchange methods, and use HttpServiceProxyFactory to create a client proxy from it for remote access over HTTP via RestClient, WebClient, or RestTemplate. On the server side, an @Controller class
docs.spring.io
이는 문서에서 확인할 수 있다.

❗️ 구현체에 대한 설명은 AI에 도움을 받았습니다.
- JdkClientHttpRequestFactory : JDK HttpClient를 쓰는 표준 구현체
- HttpComponentsClientHttpRequestFactory : Apache HttpClient 기반의 고급 튜닝, 풀링 구현체
- JettyClientHttpRequestFactory: Jetty HttpClient를 사용하는 구현체
- ReactorNettyClientRequestFactory : Reactor Netty HttpClient로 전송하는 구현체
- SimpleClientHttpRequestFactory :HttpURLConnection 기반의 가장 단순한 구현체
5가지의 구현체들이 등록될 수 있는데 그렇다면 어떤 것이 어떤 기준으로 선택되는 것일까?

공식 문서에 결정 순서가 명시돼 있다.
Request Factory를 지정하지 않았다면 클래스패스 존재 여부를 체크한다.
그리고 `HttpComponents` → `Jetty`→ `Reactor` -> `Netty` → `JDK` -> `Simple` 순서로 결정된다고 한다.

그리고 이는 내부 구현 코드에서도 명확하게 알 수 있는데

static 블록에서 ClassLoader로 각 HTTP 클라이언트 구현체의 존재 여부를 한번만 확인하고, 그 결과를 플래그로 캐싱한다. 그러므로 클래스가 최초로 메모리에 로드될때 static 블록으로 인해 클래스 패스가 존재하는지 여부가 결정되는 것이다.
Conclusion
결과적으로 build() 가 호출되는 순간에 실제 RestClient 인스턴스가 생성된다.
이는 Builder에 누적된 설정값을 기반으로 내부 요소들을 조립하고, 이후에는 설정을 변경할 수 없는 실행 전용 객체로 완성되는 것이다.
Spring 문서에서는 이렇게 생성된 RestClient 인스턴스가 여러 스레드에서 안전하게 사용될 수 있다고 명시하고 있다.
'Backend > Spring Boot' 카테고리의 다른 글
| 자바와 스프링의 비동기 처리 - 2편: CompletableFuture의 예외 처리와 타임 아웃 (4) | 2025.08.01 |
|---|---|
| 자바와 스프링의 비동기 처리 - 1편: CompletableFuture 톺아보기 (3) | 2025.07.11 |
| 스프링 이벤트를 발행하여 트랜잭션과 관심사 분리하기 (2) | 2025.04.29 |
| 동시성 문제에 대한 고찰, 점진적으로 접근하기 (0) | 2025.03.17 |