Spring Boot RestClient (ver 3.2)

์ง€๊ธˆ๊นŒ์ง€ ๋Œ€ํ‘œ์ ์œผ๋กœ ์‚ฌ์šฉํ–ˆ๋˜ HTTP ํด๋ผ์ด์–ธํŠธ ๋ชจ๋“ˆ์€ WebClient์™€ RestTemplate๊ฐ€ ๋Œ€ํ‘œ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜์—ˆ๋‹ค. ํ•˜์ง€๋งŒ RestTemplate์€ ์ด์ œ maintenance ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๊ณ  (deprecated๋Š” ์•„๋‹ˆ๋‹ค) WebClient๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ๋‹จ์ˆœํ•œ ๋™๊ธฐ ํ˜ธ์ถœ์—๋Š” ์ข€ ๊ณผํ•œ ๋ฉด์ด ์žˆ์—ˆ๋‹ค.
์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์กฐ๊ธˆ ๋” ๊ฐœ์„ ํ•ด ์ค„ ์ˆ˜ ์žˆ๋Š” HTTP ํด๋ผ์ด์–ธํŠธ ๋ชจ๋“ˆ์ด Spring Framework 6.1 (Spring Boot 3.2) ๋ถ€ํ„ฐ ์†Œ๊ฐœ๋œ RestClient๋‹ค. RestTemplate์˜ ์ง๊ด€์ ์ธ API ๋””์ž์ธ๊ณผ WebClient์™€ ๊ฐ™์ด fluent API๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋™๊ธฐ ๋ฐฉ์‹์˜ HTTP ํ†ต์‹ ์„ ์ง€์›ํ•˜๋Š” Spring Boot RestClient์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.

๋ชฉ์ฐจ

RestClient๋ฅผ ์œ„ํ•œ ๋””ํŽœ๋˜์‹œ

RestClient๋Š” spring boot 3.2๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ spring-boot-starter-web์— ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
}
Java

Http Message Conversion

spring-web ๋ชจ๋“ˆ์—๋Š” InputStream ๋ฐ OutputStream์„ ํ†ตํ•ด HTTP ์š”์ฒญ ๋ฐ ์‘๋‹ต ๋ณธ๋ฌธ์„ ์ฝ๊ณ  ์“ฐ๋Š” HttpMessageConverter ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. 
HttpMessageConverter๋Š” RestTemplate, RestClient, WebClient, @RequestBody, @ResponseBody์™€ ๊ฐ™์ด body์— ๋Œ€ํ•œ ์ง๋ ฌํ™”, ์—ญ์ง๋ ฌํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค. 
๋‹ค์Œ ๋งํฌ๋Š” Spring Boot Auto Configuration์— ์˜ํ•ด์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” HttpMessageConverter ๋นˆ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ ์„ค๋ช…์„ ํ•˜๊ณ  ์žˆ๋‹ค.
RestClient์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜๊ธฐ์— ์•ž์„œ ํ•ด๋‹น ๋งํฌ๋ฅผ ์šฐ์„  ์ฐธ๊ณ ํ•˜๋ฉด ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค.
https://docs.spring.io/spring-framework/reference/web/webmvc/message-converters.html#message-converters


RestClient์™€ HttpClient

RestClient๋Š” Spring์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ณ ์ˆ˜์ค€์˜ REST API ํด๋ผ์ด์–ธํŠธ๋‹ค.
์ด๋Š” ์‹ค์ œ ์ „์†ก ๊ณ„์ธต์—์„œ ์ „์†ก์„ ๋‹ด๋‹นํ•˜๋Š” HttpClient๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•œ ์ถ”์ƒํ™” ๊ณ„์ธต์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.
RestClient๋ฅผ ํ†ตํ•ด์„œ ์„œ๋ฒ„์™€ ํ†ต์‹ ์„ ํ•  ๋•Œ ์‹ค์ œ๋กœ ๋‚ด๋ถ€์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ ˆ์ฐจ๋ฅผ ๊ฑฐ์นœ๋‹ค.
๋ฌผ๋ก  RestTemplate, WebClient ์—ญ์‹œ ๊ณ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™” ๊ณ„์ธต์ด๊ณ  ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” HttpClient ๊ตฌํ˜„์ฒด๊ฐ€ ์ „์†ก ๊ณ„์ธต์„ ๋‹ด๋‹นํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.

RestClient -> RestClientAdapter -> ClientHttpRequestFactory -> HttpClient ๊ตฌํ˜„์ฒด
Java

์ฆ‰ RestClient๋Š” HTTP ํด๋ผ์ด์–ธํŠธ(HttpClient)๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์†Œ์œ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹ ์— Spring์˜ ์ถ”์ƒํ™”์ธ ClientHttpRequestFactory๋ฅผ ํ†ตํ•ด์„œ ์‹ค์ œ ์ „์†ก ๊ณ„์ธต์„ ์œ„์ž„ํ•œ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜  classpath์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋”ฐ๋ผ์„œ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๋Š”๋ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง„๋‹ค.

  1. Apache HttpClient
  2. Jetty HttpClient
  3. Reactor Netty HttpClient
  4. JDK client (java.net.http.HttpClient)
  5. Simple JDK client (java.net.HttpURLConnection)

ํด๋ž˜์ŠคํŒจ์Šค์— ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์กด์žฌํ•˜๊ณ  ์ „์—ญ ๊ตฌ์„ฑ์ด ์ œ๊ณต๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๊ฐ€์žฅ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‚ฌ์šฉ๋œ๋‹ค.


์ „์—ญ HttpClient ์„ค์ •

์ž๋™ ๊ฐ์ง€๋œ HTTP ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ spring.http.client.factory ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ํŒฉํ† ๋ฆฌ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, classpath์— Apache HttpClient๊ฐ€ ์žˆ์ง€๋งŒ Jetty์˜ HttpClient๋ฅผ ์„ ํ˜ธํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

spring.http.client.factory=jetty
Plaintext

๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์— ์ ์šฉ๋  ๊ธฐ๋ณธ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด ์†์„ฑ์„ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด๋‚˜ ๋ฆฌ๋””๋ ‰์…˜ ์ถ”์  ์—ฌ๋ถ€๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

spring.http.client.connect-timeout=2s
spring.http.client.read-timeout=1s
spring.http.client.redirects=dont-follow
Plaintext

Spring Boot RestClient ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

RestClient๋Š” static create ๋ฉ”์„œ๋“œ ์ค‘์—์„œ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑํ•œ๋‹ค. ๋˜ํ•œ builder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€ ์˜ต์…˜์„ ์ง€์ •ํ•œ ๋นŒ๋”๋ฅผ ์–ป์„ ์ˆ˜๋„ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉํ•  HTTP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ง€์ •, ์‚ฌ์šฉํ•  ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜๊ธฐ ์ง€์ •, ๊ธฐ๋ณธ URI ์„ค์ •, ๊ธฐ๋ณธ ๊ฒฝ๋กœ ๋ณ€์ˆ˜ ์„ค์ •, ๊ธฐ๋ณธ ์š”์ฒญ ํ—ค๋” ์„ค์ •๋“ฑ์„ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๊ณผ์ •์—์„œ ํ•  ์ˆ˜ ์žˆ๋‹ค. RestClient ์ธ์Šคํ„ด์Šค๋Š” thread safe ํ•˜๋‹ค. RestClient๋ฅผ ๋นˆ์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ  ์ƒ์„ฑ ์‹œ ๊ธฐ๋ณธ์ ์ธ ์ •๋ณด๋ฅผ ์„ธํŒ…ํ•ด ๋‘๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

// static create๋ฅผ ์ด์šฉํ•œ ์ƒ์„ฑ.
RestClient defaultClient = RestClient.create();


// builder()๋ฅผ ์ด์šฉํ•œ ์ƒ์„ฑ
RestClient customClient = RestClient.builder()
	.requestFactory(new HttpComponentsClientHttpRequestFactory())
	.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
	.baseUrl("https://example.com")
	.defaultUriVariables(Map.of("variable", "foo"))
	.defaultHeader("My-Header", "Foo")
	.defaultCookie("My-Cookie", "Bar")
	.requestInterceptor(myCustomInterceptor)
	.requestInitializer(myCustomInitializer)
	.build();
Java

WebClient ์ƒ์„ฑ ๋ฐฉ์‹๊ณผ ์ƒ๋‹นํžˆ ์œ ์‚ฌํ•จ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.


Spring Boot RestClient ๋นˆ ์ƒ์„ฑํ•˜๊ธฐ

RestClient๋Š” ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•˜๋ฏ€๋กœ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๋นˆ์„ ์ƒ์„ฑํ•˜์—ฌ ํ•„์š”ํ•œ ๊ณณ์— ์ฃผ์ž…ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹ค์Œ์€ RestClient ๋นˆ์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•œ๋‹ค.

์ง์ ‘ ์ƒ์„ฑ

๋‹ค์Œ๊ณผ ๊ฐ™์ด @Baen ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ์ง์ ‘ RestClient ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
์ด ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜ ์„ค๋ช…ํ•˜๋Š” RestClientCustomizer์— ์ง€์ •๋œ ์„ค์ •์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient myRestClient() {
        return RestClient.builder()
                .baseUrl("https://api.example.com")
                .defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("User-Agent", "MyApp/1.0")
                .defaultHeader("Authorization", "Bearer " + token)
                .build();
    }
}
Java

์ง์ ‘ RestClient.builder()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ RestClient ๋นˆ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

customizer ์‚ฌ์šฉ

์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  RestClient ๋นˆ์— ๊ณตํ†ต ์ •์ฑ…(๋กœ๊น…, ํƒ€์ž„์•„์›ƒ, ์ปค๋„ฅ์…˜ ๋“ฑ)์„ ์ ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ customizer๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. customizer๋ฅผ ํ†ตํ•ด์„œ ์„ค์ •๋œ ํ•ญ๋ชฉ์€ RestClient.builder์— ๋ฐ˜์˜๋œ๋‹ค.
customizer๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ์ ์šฉ๋œ๋‹ค.

RestClientCustomizer -> RestClient.Builder -> RestClient<br>
Plaintext

์ฃผ์˜ํ•ด์•ผ ํ•  ๋ถ€๋ถ„์€ RestClientCustomizer๋Š” RestClient.Builder์— ์ ์šฉ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
RestClient.Builder๋Š” Spring Boot Auto Configuration์— ์˜ํ•ด์„œ ์ž๋™ ์ƒ์„ฑ๋˜์ง€๋งŒ RestClient๋Š” ์ž๋™ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค. RestClient.Builder ๋นˆ์„ ์ฃผ์ž…๋ฐ›์•„ RestClient ๋นˆ์„ ์ง์ ‘ ์ƒ์„ฑํ•ด ์ค˜์•ผ ํ•œ๋‹ค.
๋‹ค์Œ์€ RestClient.Builder์— Authorization, User-Agent, Accept ํ—ค๋”๋ฅผ ๊ณตํ†ต์ ์œผ๋กœ ์ ์šฉํ•œ๋‹ค.

@Configuration
public class RestClientCommonConfig {

    @Bean
    public RestClientCustomizer authHeaderCustomizer() {
        return builder -> builder
                .defaultHeader("Authorization", "Bearer my-shared-access-token")
                .defaultHeader("User-Agent", "MyCompanyService/1.0")
                .defaultHeader("Accept", "application/json");
    }

    // RestClientCustomizer๊ฐ€ ์ ์šฉ๋œ builder๊ฐ€ ์ฃผ์ž…๋œ๋‹ค.
    @Bean
    public RestClient restClient( RestClient.Builder builder ) {
        return builder.baseUrl("https://api.example.com").build();
    }
}
Java

builder์—์„œ ์ง€์ •๋œ defaultHeader๋Š” RestClient.Builder์— ๋ฐ˜์˜์ด ๋˜๊ณ  RestClient.Builder๋ฅผ ํ†ตํ•ด์„œ ์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  RestClient์— ์ ์šฉ๋œ๋‹ค. ์ฆ‰ RestClient.Builder ๋นˆ์„ ํ†ตํ•ด์„œ RestClient ๋นˆ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ RestClientCustomizer ์„ค์ •์ด ์ ์šฉ๋œ๋‹ค.
๋‹ค์Œ ์ฝ”๋“œ๋Š” JDK11 ์ด์ƒ๋ถ€ํ„ฐ ์ œ๊ณต๋˜๋Š” ๋‚ด์žฅ๋œ HttpClient ์ธ์Šคํ„ด์Šค ๋นˆ์„ ์ƒ์„ฑํ•˜๊ณ  ์ด ๋นˆ์„ JdkClientHttpRequestFactory์— ์ ์šฉ ํ›„ RestClientCustomizer์— ์ ์šฉํ•œ๋‹ค.

package org.example.spring.restclient.sample.configuration;

import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

import java.net.http.HttpClient;
import java.time.Duration;

@Configuration
public class RestClientConfiguration {

    @Bean
    public HttpClient httpClient() {
        return HttpClient.newBuilder()
                .connectTimeout( Duration.ofSeconds( 3 ) )
                .followRedirects( HttpClient.Redirect.NORMAL )
                .version( HttpClient.Version.HTTP_2 )
                .build();
    }

    @Bean
    public JdkClientHttpRequestFactory clientHttpRequestFactory( HttpClient httpClient ) {
        JdkClientHttpRequestFactory jdkClientHttpRequestFactory = new JdkClientHttpRequestFactory( httpClient );
        jdkClientHttpRequestFactory.setReadTimeout( Duration.ofSeconds( 5 ) );
        return jdkClientHttpRequestFactory;
    }

    @Bean
    public RestClientCustomizer restClientCustomizer( JdkClientHttpRequestFactory requestFactory ) {
        return builder ->
                builder.requestFactory( requestFactory )
                        .defaultHeader( "Authorization", "Bearer " + System.getenv( "BEARER_TOKEN" ) )
                        .defaultHeader( "User-Agent", System.getenv( "USER_AGENT" ) )
                        .defaultHeader( "Accept", "application/json" );
    }

    // ์‚ฌ์šฉ์ž ์„œ๋น„์Šค API ํด๋ผ์ด์–ธํŠธ
    @Bean
    public RestClient userApiClient(RestClient.Builder builder) {
        return builder
                .baseUrl("https://api.example.com/users") // ์„œ๋น„์Šค๋ณ„ Base URL ์ง€์ •
                .build();
    }

    // ์ฃผ๋ฌธ ์„œ๋น„์Šค API ํด๋ผ์ด์–ธํŠธ
    @Bean
    public RestClient orderApiClient(RestClient.Builder builder) {
        return builder
                .baseUrl("https://api.example.com/orders")
                .build();
    }

    // ๊ฒฐ์ œ ์„œ๋น„์Šค API ํด๋ผ์ด์–ธํŠธ
    @Bean
    public RestClient paymentApiClient(RestClient.Builder builder) {
        return builder
                .baseUrl("https://api.example.com/payments")
                .build();
    }
}
Java

๋ฐ˜๋ณตํ•˜์—ฌ ์„ค๋ช…ํ•˜์ง€๋งŒ RestClientCustomizer ๋นˆ์€ RestClient.Builder ๋นˆ์— ์ ์šฉ๋œ๋‹ค.
๋”ฐ๋ผ์„œ RestClient.Builder๋ฅผ ํ†ตํ•ด์„œ ์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  RestClient ๋นˆ์—๋Š” RestClientCustomizer๊ฐ€ ๊ณตํ†ต์ ์œผ๋กœ ์ ์šฉ๋œ๋‹ค.
์œ„ ์ƒ˜ํ”Œ ์ฝ”๋“œ๋Š” HttpClient ์„ค์ •(ํƒ€์ž„์•„์›ƒ, ๋ฒ„์ „๋“ฑ)์„ RestClient.Builder ๋นˆ์— ๊ณต์œ ํ•œ๋‹ค.
๋ณ„๋„์˜ RestClient๋นˆ์—๋Š” baseUrl๋งŒ ์ง€์ •ํ•˜๋ฉด ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.


Spring Boot RestClient ์‚ฌ์šฉ

Path Variable ์ง€์ •

RestClient๋Š” RestTemplate์ฒ˜๋Ÿผ URI ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜๋ฅผ ์ง์ ‘ ์น˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

User user = RestClient.create()
        .get()
        .uri("https://api.example.com/users/{id}", 12345) // ์ˆœ์„œ๋Œ€๋กœ ์น˜ํ™˜
        .retrieve()
        .body(User.class);
Java

Map์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฆ„ ๊ธฐ๋ฐ˜์œผ๋กœ ์น˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

Map<String, Object> uriVariables = Map.of("userId", 12345);

User user = RestClient.create()
        .get()
        .uri("https://api.example.com/users/{userId}", uriVariables)
        .retrieve()
        .body(User.class);
Java

UriBuilder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ PathVariable์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

User user = RestClient.create()
        .get()
        .uri(uriBuilder -> uriBuilder
                .path("/users/{id}")
                .build(12345))
        .retrieve()
        .body(User.class);
Java

Query Parameter ์ง€์ •

uri()์— ์ง์ ‘ ํ•˜๋“œ์ฝ”๋“œ ๋ฐฉ์‹์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ถŒ์žฅํ•˜์ง„ ์•Š๋Š”๋‹ค.

List<User> users = RestClient.create()
        .get()
        .uri("https://api.example.com/users?active=true&limit=10")
        .retrieve()
        .body(new ParameterizedTypeReference<List<User>>() {});
Java

UriBuilder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Query Parameter๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ถŒ์žฅ ๋ฐฉ์‹)

List<User> users = RestClient.create()
        .get()
        .uri(uriBuilder -> uriBuilder
                .path("/users")
                .queryParam("active", true)
                .queryParam("limit", 10)
                .queryParam("sort", "name")
                .build())
        .retrieve()
        .body(new ParameterizedTypeReference<List<User>>() {});
Java

UriBuilder๋Š” ๊ฐ™์€ ์ด๋ฆ„์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

List<String> roles = List.of("ADMIN", "USER");

List<User> users = RestClient.create()
        .get()
        .uri(uriBuilder -> uriBuilder
                .path("/users")
                .queryParam("role", roles.toArray()) // role=ADMIN&role=USER
                .build())
        .retrieve()
        .body(new ParameterizedTypeReference<List<User>>() {});
Java

PathVariable + Query Parameter ํ˜ผํ•ฉ

UriBuilder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜ผํ•ฉํ•˜์—ฌ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

UserDetail detail = RestClient.create()
        .get()
        .uri(uriBuilder -> uriBuilder
                .path("/users/{id}")
                .queryParam("include", "profile")
                .queryParam("include", "roles")
                .build(12345))
        .retrieve()
        .body(UserDetail.class);
Java

Request headers and body

header(String, String), headers(Consumer, <HttpHeaders>)๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ accept(MediaType), acceptCharset(Charset…)๊ณผ ๊ฐ™์€ ํŽธ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์š”์ฒญ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ณธ๋ฌธ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” HTTP ์š”์ฒญ (POST, PUT, PATCH)์˜ ๊ฒฝ์šฐ contentType(MediaType) ๋ฐ contentLength(long)๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์š”์ฒญ ๋ณธ๋ฌธ ์ž์ฒด๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ HTTP ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜์„ ์‚ฌ์šฉํ•˜๋Š” body(Object)๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ  ๋˜๋Š” ParameterizedTypeReference๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ ๋ณธ๋ฌธ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๋ฅผ ํ†ตํ•ด ์ œ๋„ˆ๋ฆญ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ๋ฌธ์„ OutputStream์— ๊ธฐ๋กํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋กœ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

ParameterizedTypeReference๋ž€?
ParameterizedTypeReference๋Š” Spring Framework์—์„œ ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ •๋ณด๋ฅผ ๋Ÿฐํƒ€์ž„์— ๋ณด์กดํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋‹ค.
ํƒ€์ž… ์†Œ๊ฑฐ
– Java์˜ ์ œ๋„ค๋ฆญ์€ ์ปดํŒŒ์ผ ํƒ€์ž„์—๋งŒ ์กด์žฌํ•˜๊ณ  ๋Ÿฐํƒ€์ž„์—๋Š” ํƒ€์ž… ์ •๋ณด๊ฐ€ ์†Œ๊ฑฐ๋œ๋‹ค.
//์ปดํŒŒ์ผ ํƒ€์ž„
List<User> users = new ArrayList<User>();
//๋Ÿฐํƒ€์ž„ – ํƒ€์ž… ์ •๋ณด ์†Œ์‹ค (User ์ •๋ณด๊ฐ€ ์‚ฌ๋ผ์ง)
List<User> users = new ArrayList<>();

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ
// List<User>.class๋Š” ๋ถˆ๊ฐ€๋Šฅ
List<User> users = restClient.get().uri(“/users”).retrieve().body(List<User>.class);  
// ํƒ€์ž… ์ •๋ณด ์†์‹ค
List<User> users = restClient.get().uri(“/users”).retrieve().body(List.class) // List<Object>๋กœ ์ธ์‹๋จ

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋กœ ์ธํ•œ ๊ฒฝ์šฐ ParameterizedTypeReference๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
List<User> users = restClient.get().uri(“/users”).retrieve()
                    .body(new ParameterizedTypeReference<List<User>>() {});
ParameterizedTypeReference์— ๋Œ€ํ•œ ์ต๋ช… ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ •๋ณด๋ฅผ ๋ณด์กดํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

header + JSON Body

๊ฐ€์žฅ ํ”ํ•œ ์ผ€์ด์Šค๋กœ POST ์š”์ฒญ ์‹œ Content-Type, Authorization ๋“ฑ์˜ ํ—ค๋”์™€ DTO ํ˜•ํƒœ์˜ JSON body๋ฅผ ํ•จ๊ป˜ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

UserRequest requestBody = new UserRequest("test", "developer");

UserResponse response = restClient.post()
        .uri("https://api.example.com/users")
        .contentType(MediaType.APPLICATION_JSON)          // Content-Type ์„ค์ •
        .accept(MediaType.APPLICATION_JSON)               // Accept ์„ค์ •
        .header("Authorization", "Bearer " + accessToken) // Custom Header
        .body(requestBody)                                // Body ์ง๋ ฌํ™” (์ž๋™ JSON ๋ณ€ํ™˜)
        .retrieve()
        .body(UserResponse.class);                        // ์‘๋‹ต ์—ญ์ง๋ ฌํ™”
Java

body(Object) ๋ฉ”์„œ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ HttpMessageConverter๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ JSON ์ง๋ ฌํ™”๋œ๋‹ค.

Map<String, Object>๋กœ Body๋ฅผ ์ง์ ‘ ๊ตฌ์„ฑ

Map<String, Object> payload = Map.of(
    "name", "test",
    "role", "developer"
);

String result = restClient.post()
        .uri("https://api.example.com/users")
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers -> {
            headers.setBearerAuth("your-jwt-token");
            headers.add("X-Custom-Header", "example");
        })
        .body(payload)
        .retrieve()
        .body(String.class);
Java

headers()๋Š” Consumer<HttpHeaders>๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค. ๊ฐ„๋‹จํ•œ ํ—ค๋” ์„ค์ •์€ .header(key, value) ์„ค์ •์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค.

Form URL Encoded ์ „์†ก (application/x-www-form-urlencoded)

Map ํ˜•ํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋˜, Content-Type์„ ํผ ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•œ๋‹ค.

MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "user1");
formData.add("password", "secret");

String tokenResponse = restClient.post()
        .uri("https://auth.example.com/token")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .accept(MediaType.APPLICATION_JSON)
        .body(formData)
        .retrieve()
        .body(String.class);
Java

๋‚ด๋ถ€์ ์œผ๋กœ FormHttpMessageConverter๊ฐ€ ์ž๋™์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

Raw Body (๋ฌธ์ž์—ด ์ง์ ‘ ์ง€์ •)

์ด๋ฏธ ์ง๋ ฌํ™”๋œ JSON ๋ฌธ์ž์—ด์„ ์ง์ ‘ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค.

String rawJson = """
{
  "email": "test@example.com",
  "active": true
}
""";

String result = restClient.post()
        .uri("https://api.example.com/register")
        .contentType(MediaType.APPLICATION_JSON)
        .body(rawJson)
        .retrieve()
        .body(String.class);
Java

Binary Body (์˜ˆ: ํŒŒ์ผ ์—…๋กœ๋“œ)

InputStream ๋˜๋Š” byte[]๋ฅผ ํ†ตํ•ด์„œ body๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค.

Path filePath = Path.of("/tmp/sample.pdf");
byte[] fileBytes = Files.readAllBytes(filePath);

String response = restClient.post()
        .uri("https://api.example.com/upload")
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .header("X-Filename", filePath.getFileName().toString())
        .body(fileBytes)
        .retrieve()
        .body(String.class);
Java

์ด ๋ฐฉ์‹์€ multipart ์—…๋กœ๋“œ๊ฐ€ ์•„๋‹Œ raw binary ์ „์†ก์ด๋‹ค.

OutputStream์„ ์ด์šฉํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ

Path filePath = Path.of("/tmp/large-video.mp4");

client.post()
      .uri("https://api.example.com/upload")
      .contentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM)
      .body(outputStream -> {
          try (InputStream inputStream = Files.newInputStream(filePath)) {
              inputStream.transferTo(outputStream); // Input โ†’ OutputStream ๋ณต์‚ฌ
          }
      })
      .retrieve()
      .toBodilessEntity(); // ์‘๋‹ต ๋ณธ๋ฌธ์ด ํ•„์š” ์—†์„ ๋•Œ
Java

์—…๋กœ๋“œ ํŒŒ์ผ ์ „์ฒด์ธจ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ์ง€ ์•Š๊ณ  ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ๋ฐ”๋กœ ์ „์†กํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ์—…๋กœ๋“œ์— ์ ํ•ฉํ•˜๋‹ค.

Multipart/Form-Data (ํŒŒ์ผ + JSON์ „์†ก)

MultipartBodyBuilder๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ„๋‹จํžˆ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", new FileSystemResource("/tmp/photo.png"));
builder.part("meta", "{\"author\":\"myname\"}", MediaType.APPLICATION_JSON);

String result = restClient.post()
        .uri("https://api.example.com/upload")
        .contentType(MediaType.MULTIPART_FORM_DATA)
        .body(builder.build())
        .retrieve()
        .body(String.class);
Java

Retrieving the Response (์‘๋‹ต ๊ฐ€์ ธ์˜ค๊ธฐ)

๊ธฐ๋ณธ body(class<T>) ์‚ฌ์šฉ

๊ฐ€์žฅ ๋‹จ์ˆœํ•˜๊ณ  ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์ด๋‹ค.
์‘๋‹ต์˜ Content-Type(application/json, text/plain๋“ฑ)์— ๋”ฐ๋ผ HttpMessageConverter๊ฐ€ ์ž๋™์œผ๋กœ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

User user = restClient.get()
        .uri("https://api.example.com/users/1")
        .retrieve()
        .body(User.class); // JSON โ†’ User ๊ฐ์ฒด
Java

Content-Type์ด JSON์ด๋ฉด ObjectMapper๋ฅผ ํ†ตํ•ด์„œ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

body (ParameterizedTypeReference<T>) ์‚ฌ์šฉ (์ œ๋„ˆ๋ฆญ ์ปฌ๋ ‰์…˜ ๋Œ€์‘)

์‘๋‹ต์ด ๋ฆฌ์ŠคํŠธ ํ˜น์€ ๋งต ํ˜•ํƒœ์ผ ๊ฒฝ์šฐ ์ž๋ฐ”์˜ ํƒ€์ž… ์†Œ๊ฑฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ParameterizedTypeReference๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

List<User> users = restClient.get()
        .uri("https://api.example.com/users")
        .retrieve()
        .body(new ParameterizedTypeReference<List<User>>() {});
Java

List<User> ํ˜น์€ Map<String, Object>์™€ ๊ฐ™์€ ๋ณตํ•ฉ ํƒ€์ž…๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์—ญ์ง๋ ฌํ™” ํ•œ๋‹ค.
Spring์˜ ParameterizedTypeReference๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ TypeReference๋ฅผ ์œ ์ง€ํ•ด ์ค€๋‹ค.

exchange() ํ˜น์€ toEntity()๋กœ Raw Body ์ง์ ‘ ์ฒ˜๋ฆฌ

์‘๋‹ต ์ฝ”๋“œ, ์ƒํƒœ ์ฝ”๋“œ ๋“ฑ์„ ํ•จ๊ป˜ ๋‹ค๋ฃจ๋ฉด์„œ ์ง์ ‘ body๋ฅผ ํŒŒ์‹ฑํ•˜๊ณ ์ž ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.

ResponseEntity<String> response = restClient.get()
        .uri("https://api.example.com/raw")
        .retrieve()
        .toEntity(String.class);

if (response.getStatusCode().is2xxSuccessful()) {
    String rawJson = response.getBody();
    User user = new ObjectMapper().readValue(rawJson, User.class);
}
Java

String ํƒ€์ž…์œผ๋กœ body payload๋ฅผ ๋ฐ›์€ ๋’ค ์ง์ ‘ Jackson/Gson์œผ๋กœ ์—ญ์ง๋ ฌํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋น„ํ‘œ์ค€ ์‘๋‹ต (JSON + Base64, JSON + Hex๋“ฑ)์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค.

OutputStream์„ ํ†ตํ•œ body ์ˆ˜์‹ 

๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์ด๋‚˜ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์„ ๋ฐ›์„ ๋•Œ๋Š” OutputStream์„ ์ง์ ‘ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋ฉ”๋ชจ๋ฆฌ์— ์ „์ฒด body๋ฅผ ์˜ฌ๋ฆฌ์ง€ ์•Š๊ณ  ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋กํ•˜๊ธฐ ๋•Œ๋ฌธ์— OutOfMemoryError๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

restClient.get()
        .uri("https://example.com/large-file.zip")
        .retrieve()
        .body((inputStream, headers) -> {
            try (OutputStream outputStream =
                         new FileOutputStream("/tmp/large-file.zip")) {
                inputStream.transferTo(outputStream);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
            return null; // body()๋Š” ๋ฐ˜ํ™˜๊ฐ’์ด ํ•„์š”ํ•˜๋ฏ€๋กœ null ๋ฆฌํ„ด
        });
Java

body(BiFunction<InputStream, HttpHeaders, T>) ํ˜•ํƒœ๋ฅผ ์ง€์›ํ•œ๋‹ค. ์ฆ‰, ์ž…๋ ฅ์ŠคํŠธ๋ฆผ์„ ์ง์ ‘ ์ฝ๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
exchange()๋ฅผ ์ด์šฉํ•˜์—ฌ ์ €์ˆ˜์ค€ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

restClient.get()
        .uri("https://example.com/large-file.zip")
        .exchange((request, response) -> {
            try (InputStream in = response.body();
                 OutputStream out = new FileOutputStream("/tmp/large-file.zip")) {
                byte[] buffer = new byte[8192];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            }
            return null;
        });
Java

๋ณด๋‹ค ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” exchange()๋ฅผ ์ด์šฉํ•ด์„œ ClientHttpResponse๋ฅผ ์ง์ ‘ ๋‹ค๋ฃฌ๋‹ค.
exchange()๋Š” ์ƒํƒœ์ฝ”๋“œ, ํ—ค๋”, body ์ŠคํŠธ๋ฆผ์„ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.
์ผ๋ฐ˜์ ์ธ retrieve()๋ณด๋‹ค ์ €์ˆ˜์ค€์ด๋ฉฐ ๋น„๋™๊ธฐ/์ŠคํŠธ๋ฆฌ๋ฐ ์ฒ˜๋ฆฌ์—๋„ ์ ํ•ฉํ•˜๋‹ค.

Error Handling (์—๋Ÿฌ ์ฒ˜๋ฆฌ)

๊ธฐ๋ณธ์ ์œผ๋กœ RestClient๋Š” 4xx ๋˜๋Š” 5xx ์ƒํƒœ ์ฝ”๋“œ์˜ ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ RestClientException์˜ ํ•˜์œ„ ํด๋ž˜์Šค๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
์ด ๋™์ž‘์€ onStatus()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์žฌ์ •์˜ ํ•  ์ˆ˜ ์žˆ๋‹ค.

String result = restClient.get()
	.uri("https://example.com/this-url-does-not-exist")
	.retrieve()
	.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
		throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
	})
	.body(String.class);
Java

์œ„ ์ฝ”๋“œ๋Š” 4xx ์‘๋‹ต ์ฝ”๋“œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ MyCustomRuntimeException์„ ๋ฐœ์ƒ์‹œํ‚ค๋‹ค.

Exchange

๊ณ ๊ธ‰ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” RestClient๊ฐ€ retrieve() ๋Œ€์‹ ์— exchange() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ธฐ๋ณธ HTTP ์š”์ฒญ ๋ฐ ์‘๋‹ต์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ๊ณตํ•œ๋‹ค.
exchange() ์‚ฌ์šฉ ์‹œ ์ƒํƒœ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.
exchange() ๋ฉ”์„œ๋“œ๊ฐ€ ์ด๋ฏธ ์ „์ฒด ์‘๋‹ต์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ํ•„์š”ํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ์ง์ ‘ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

Pet result = restClient.get()
	.uri("https://petclinic.example.com/pets/{id}", id)
	.accept(APPLICATION_JSON)
	.exchange((request, response) -> {
		if (response.getStatusCode().is4xxClientError()) {
			throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
		}
		else {
			Pet pet = convertResponse(response);
			return pet;
		}
	});
Java

exchange() ๋ฉ”์„œ๋“œ๋Š” request, response๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ œ๊ณตํ•˜๋Š” ๋žŒ๋‹ค ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์š”์ฒญ, ์‘๋‹ต์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋™์ ์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•œ๋‹ค.

RestTemplate ์—์„œ RestClient ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#_migrating_from_resttemplate_to_restclient ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ๋„์›€์ด ๋  ๊ฒƒ์ด๋‹ค.

๋.


์ฐธ๊ณ ๋งํฌ
https://docs.spring.io/spring-framework/reference/integration/rest-clients.html