REST Client & HTTP

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 29 daqiqa o'qish

REST client - Spring Boot application boshqa servis yoki tashqi API’ga HTTP so‘rov yuborishi uchun ishlatiladi.

Bu bo‘lim ichida quyidagilar bor: RestTemplate, WebClient, HttpInterface, timeout va retry configuration.


1. REST client nima?

Oldingi darslarda biz REST API yozdik:

Frontend / Mobile App → Spring Boot API → Database

Lekin real loyihada backend faqat database bilan ishlamaydi. U boshqa servislar bilan ham gaplashadi:

Spring Boot API → Payment service
Spring Boot API → SMS service
Spring Boot API → Telegram bot API
Spring Boot API → CRM service
Spring Boot API → Currency exchange API
Spring Boot API → Delivery service

Mana shu tashqi servisga HTTP request yuboradigan qism REST client deyiladi.


2. Server va client farqi

Biz server bo‘lgan holat

Frontend bizga request yuboradi:

GET /api/products

Bizning controller javob qaytaradi:

@GetMapping("/api/products")
public List<ProductResponse> getProducts() {
    return productService.getProducts();
}

Bu yerda biz servermiz.


Biz client bo‘lgan holat

Biz boshqa servisga request yuboramiz:

GET https://api.example.com/currency/USD

Bu yerda biz clientmiz.

Bizning Spring Boot app → tashqi API

3. HTTP qisqa eslatma

REST client ishlatishdan oldin HTTP asoslarini bilish kerak.

HTTP methodlar

Method

Ma’nosi

Misol

GET

Ma’lumot olish

Product list olish

POST

Yangi ma’lumot yaratish

Order yaratish

PUT

To‘liq update

Product update

PATCH

Qisman update

Order status update

DELETE

O‘chirish

Product delete


HTTP status code

Status

Ma’nosi

200 OK

Muvaffaqiyatli

201 Created

Yaratildi

400 Bad Request

Request noto‘g‘ri

401 Unauthorized

Login kerak

403 Forbidden

Ruxsat yo‘q

404 Not Found

Topilmadi

409 Conflict

Conflict, masalan duplicate

500 Internal Server Error

Server xatosi

503 Service Unavailable

Servis vaqtincha ishlamayapti

REST client yozganda status codelarni to‘g‘ri ushlash juda muhim.


4. Spring’da REST client variantlari

Spring’da bir nechta HTTP client bor:

RestTemplate
RestClient
WebClient
HttpInterface

Spring Framework 6.1+ da RestClient ham bor, shuni ham bilib qo‘yish foydali.

Client

Turi

Qachon ishlatiladi

RestTemplate

Synchronous / blocking

Legacy projectlarda ko‘p uchraydi

RestClient

Synchronous / blocking

Modern Spring’da RestTemplate o‘rniga qulayroq

WebClient

Reactive / non-blocking

Reactive, async, streaming, high-load

HttpInterface

Declarative client

Interface yozib, boilerplate kamaytirish

Spring rasmiy hujjatlarida RestTemplate synchronous HTTP client ekani aytilgan. WebClient esa non-blocking reactive client sifatida ta’riflanadi. (Home)


5. Blocking va non-blocking farqi

Blocking

Request yuboriladi va thread javob kelguncha kutadi.

Thread → request yubordi → javob kutdi → davom etdi

RestTemplate va RestClient blocking client hisoblanadi.

Oddiy backend API’lar uchun tushunish oson.


Non-blocking

Request yuboriladi, lekin thread bekor kutib turmaydi.

Thread → request yubordi → boshqa ishga o‘tdi
Javob kelganda callback/reactive pipeline ishlaydi

WebClient non-blocking reactive client. Rasmiy hujjatlarda WebClient request bajarish uchun HTTP client library kerak qilishi va Reactor Netty, JDK HttpClient, Jetty Reactive HttpClient kabi variantlarni qo‘llashi aytiladi. (Home)


6. Qaysi birini tanlash kerak?

Java developer uchun amaliy qoida:

Legacy project → RestTemplate ko‘rasiz
Oddiy modern blocking API → RestClient
Reactive/WebFlux project → WebClient
Boilerplate kamaytirish → HttpInterface

Agar siz hozir Spring Boot MVC bilan oddiy REST API yozayotgan bo‘lsangiz:

RestTemplate’ni tushuning
RestClient/WebClient’ni ham bilib qo‘ying
HttpInterface bilan tanishing

7. Example scenario

Tasavvur qilamiz, bizda Product API bor. U tashqi Currency APIdan kurs olib, product narxini USD’dan UZS’ga o‘tkazadi.

Tashqi API taxminan shunday response qaytaradi:

{
  "base": "USD",
  "target": "UZS",
  "rate": 12650.50
}

DTO:

import java.math.BigDecimal;

public record CurrencyRateResponse(
        String base,
        String target,
        BigDecimal rate
) {
}

8. RestTemplate

RestTemplate eski, lekin hali ko‘p loyihalarda uchraydi. Spring hujjatlarida u HTTP request bajaradigan synchronous client sifatida berilgan. (Home)

Dependency

Agar sizda spring-boot-starter-web bo‘lsa, odatda kerakli web client classlar mavjud bo‘ladi.


Bean yaratish

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestClientConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

GET request

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CurrencyClient {

    private final RestTemplate restTemplate;

    public CurrencyClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public CurrencyRateResponse getUsdToUzsRate() {
        String url = "https://api.example.com/currency/USD/UZS";

        return restTemplate.getForObject(url, CurrencyRateResponse.class);
    }
}

Bu yerda:

getForObject → GET request yuboradi va response body’ni objectga aylantiradi

ResponseEntity bilan olish

Agar status, header ham kerak bo‘lsa:

import org.springframework.http.ResponseEntity;

public CurrencyRateResponse getUsdToUzsRate() {
    String url = "https://api.example.com/currency/USD/UZS";

    ResponseEntity<CurrencyRateResponse> response =
            restTemplate.getForEntity(url, CurrencyRateResponse.class);

    if (!response.getStatusCode().is2xxSuccessful()) {
        throw new BadRequestException("Currency API xato qaytardi");
    }

    return response.getBody();
}

POST request

Masalan SMS servisga request yuboramiz.

Request DTO:

public record SmsSendRequest(
        String phone,
        String message
) {
}

Response DTO:

public record SmsSendResponse(
        String id,
        String status
) {
}

Client:

public SmsSendResponse sendSms(String phone, String message) {
    String url = "https://sms.example.com/api/send";

    SmsSendRequest request = new SmsSendRequest(phone, message);

    return restTemplate.postForObject(
            url,
            request,
            SmsSendResponse.class
    );
}

9. exchange() method

exchange() ko‘proq nazorat beradi: method, headers, body, response type.

import org.springframework.http.*;
import org.springframework.stereotype.Service;

@Service
public class SmsClient {

    private final RestTemplate restTemplate;

    public SmsClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public SmsSendResponse sendSms(String phone, String message) {
        String url = "https://sms.example.com/api/send";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setBearerAuth("token-value");

        SmsSendRequest body = new SmsSendRequest(phone, message);

        HttpEntity<SmsSendRequest> entity = new HttpEntity<>(body, headers);

        ResponseEntity<SmsSendResponse> response = restTemplate.exchange(
                url,
                HttpMethod.POST,
                entity,
                SmsSendResponse.class
        );

        return response.getBody();
    }
}

Bu yerda:

HttpHeaders → headerlar
HttpEntity  → body + header
exchange    → to‘liq HTTP request

10. RestTemplate timeout

Timeout juda muhim. Timeout bo‘lmasa, tashqi API osilib qolsa, sizning servis ham kutib qoladi.

Minimal config:

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
                .connectTimeout(Duration.ofSeconds(2))
                .readTimeout(Duration.ofSeconds(5))
                .build();
    }
}

Connect timeout

Tashqi serverga ulanish uchun maksimal kutish vaqti

Read timeout

Ulanib bo‘lgandan keyin response kelishini kutish vaqti

Amaliy qoida:

Timeoutsiz HTTP client - production’da xavfli.

11. RestTemplate xatolarini ushlash

RestTemplate 4xx/5xx statuslarda exception tashlashi mumkin.

Masalan:

import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;

public CurrencyRateResponse getUsdToUzsRate() {
    try {
        return restTemplate.getForObject(
                "https://api.example.com/currency/USD/UZS",
                CurrencyRateResponse.class
        );
    } catch (HttpClientErrorException ex) {
        throw new BadRequestException("Currency API client xato: " + ex.getStatusCode());
    } catch (HttpServerErrorException ex) {
        throw new ExternalServiceException("Currency API server xato");
    } catch (ResourceAccessException ex) {
        throw new ExternalServiceException("Currency API timeout yoki network xato");
    }
}

Custom exception:

public class ExternalServiceException extends RuntimeException {

    public ExternalServiceException(String message) {
        super(message);
    }
}

12. WebClient

WebClient - modern, reactive HTTP client.

Rasmiy Javadoc’da WebClient non-blocking, reactive API ekanligi aytiladi. (Home)

Dependency

WebClient uchun odatda:

implementation 'org.springframework.boot:spring-boot-starter-webflux'

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

MVC projectda ham WebClient ishlatish mumkin. Faqat reactive dependency qo‘shiladi.


WebClient bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient currencyWebClient() {
        return WebClient.builder()
                .baseUrl("https://api.example.com")
                .build();
    }
}

GET request

Reactive ko‘rinish:

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class CurrencyWebClient {

    private final WebClient webClient;

    public CurrencyWebClient(WebClient currencyWebClient) {
        this.webClient = currencyWebClient;
    }

    public Mono<CurrencyRateResponse> getUsdToUzsRate() {
        return webClient.get()
                .uri("/currency/USD/UZS")
                .retrieve()
                .bodyToMono(CurrencyRateResponse.class);
    }
}

Bu Mono<CurrencyRateResponse> qaytaradi.


MVC projectda .block() ishlatish

Agar siz oddiy Spring MVC projectda bo‘lsangiz va service method oddiy object qaytarishi kerak bo‘lsa:

public CurrencyRateResponse getUsdToUzsRateBlocking() {
    return webClient.get()
            .uri("/currency/USD/UZS")
            .retrieve()
            .bodyToMono(CurrencyRateResponse.class)
            .block();
}

Lekin ehtiyot bo‘ling:

WebFlux reactive flow ichida block() ishlatish yomon.
MVC projectda tashqi API chaqirish uchun ba’zan ishlatiladi.

13. WebClient error handling

import org.springframework.http.HttpStatusCode;
import reactor.core.publisher.Mono;

public Mono<CurrencyRateResponse> getUsdToUzsRate() {
    return webClient.get()
            .uri("/currency/USD/UZS")
            .retrieve()
            .onStatus(
                    HttpStatusCode::is4xxClientError,
                    response -> Mono.error(
                            new BadRequestException("Currency API client xato")
                    )
            )
            .onStatus(
                    HttpStatusCode::is5xxServerError,
                    response -> Mono.error(
                            new ExternalServiceException("Currency API server xato")
                    )
            )
            .bodyToMono(CurrencyRateResponse.class);
}

14. WebClient timeout

Reactor Netty bilan timeout config:

import io.netty.channel.ChannelOption;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import java.time.Duration;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient currencyWebClient() {
        HttpClient httpClient = HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
                .responseTimeout(Duration.ofSeconds(5));

        return WebClient.builder()
                .baseUrl("https://api.example.com")
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }
}

Bu yerda:

CONNECT_TIMEOUT_MILLIS → ulanish timeout
responseTimeout       → response kutish timeout

15. WebClient POST request

public Mono<SmsSendResponse> sendSms(String phone, String message) {
    SmsSendRequest request = new SmsSendRequest(phone, message);

    return webClient.post()
            .uri("/api/send")
            .header("Authorization", "Bearer token-value")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(SmsSendResponse.class);
}

Blocking variant:

public SmsSendResponse sendSmsBlocking(String phone, String message) {
    return webClient.post()
            .uri("/api/send")
            .header("Authorization", "Bearer token-value")
            .bodyValue(new SmsSendRequest(phone, message))
            .retrieve()
            .bodyToMono(SmsSendResponse.class)
            .block();
}

16. RestClient

Spring Framework 6.1'dan boshlab RestClient bor. Rasmiy hujjatda RestClient synchronous HTTP client bo‘lib, fluent API orqali request bajarishi aytilgan. (Home)

Bu mavzuda alohida ko‘rsatilmagan, lekin hozirgi Spring loyihalarda bilish foydali.

Bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient currencyRestClient() {
        return RestClient.builder()
                .baseUrl("https://api.example.com")
                .build();
    }
}

GET request

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class CurrencyRestClient {

    private final RestClient restClient;

    public CurrencyRestClient(RestClient currencyRestClient) {
        this.restClient = currencyRestClient;
    }

    public CurrencyRateResponse getUsdToUzsRate() {
        return restClient.get()
                .uri("/currency/USD/UZS")
                .retrieve()
                .body(CurrencyRateResponse.class);
    }
}

Bu RestTemplatega qaraganda ko‘proq fluent va o‘qilishi qulay.


17. HttpInterface

HttpInterface - declarative HTTP client. Ya’ni siz interface yozasiz, Spring implementation’ni proxy orqali yaratadi.

Rasmiy hujjatlarda HTTP Interface client’lar REST client ustida declarative HTTP service interface yaratish usuli sifatida berilgan. (Home)

Interface

import org.springframework.web.service.annotation.GetExchange;

public interface CurrencyApiClient {

    @GetExchange("/currency/USD/UZS")
    CurrencyRateResponse getUsdToUzsRate();
}

Agar path variable kerak bo‘lsa:

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;

public interface CurrencyApiClient {

    @GetExchange("/currency/{base}/{target}")
    CurrencyRateResponse getRate(
            @PathVariable String base,
            @PathVariable String target
    );
}

HttpInterface config

Spring 6'da HttpServiceProxyFactory orqali proxy yaratiladi.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class HttpInterfaceConfig {

    @Bean
    public RestClient currencyRestClient() {
        return RestClient.builder()
                .baseUrl("https://api.example.com")
                .build();
    }

    @Bean
    public CurrencyApiClient currencyApiClient(RestClient currencyRestClient) {
        HttpServiceProxyFactory factory = HttpServiceProxyFactory
                .builderFor(RestClientAdapter.create(currencyRestClient))
                .build();

        return factory.createClient(CurrencyApiClient.class);
    }
}

Service’da ishlatish:

@Service
public class CurrencyService {

    private final CurrencyApiClient currencyApiClient;

    public CurrencyService(CurrencyApiClient currencyApiClient) {
        this.currencyApiClient = currencyApiClient;
    }

    public BigDecimal getUsdRate() {
        CurrencyRateResponse response = currencyApiClient.getRate("USD", "UZS");
        return response.rate();
    }
}

18. HttpInterface POST request

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.PostExchange;

public interface SmsApiClient {

    @PostExchange("/api/send")
    SmsSendResponse sendSms(@RequestBody SmsSendRequest request);
}

Service:

@Service
public class SmsService {

    private final SmsApiClient smsApiClient;

    public SmsService(SmsApiClient smsApiClient) {
        this.smsApiClient = smsApiClient;
    }

    public void sendOrderCreatedSms(String phone) {
        SmsSendResponse response = smsApiClient.sendSms(
                new SmsSendRequest(phone, "Order yaratildi")
        );

        if (!"SUCCESS".equals(response.status())) {
            throw new ExternalServiceException("SMS yuborilmadi");
        }
    }
}

19. Config bilan base URL berish

Hardcode yomon:

.baseUrl("https://api.example.com")

Yaxshiroq:

application.yml:

external:
  currency:
    base-url: https://api.example.com
  sms:
    base-url: https://sms.example.com
    token: ${SMS_TOKEN}

Properties:

import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "external.currency")
public record CurrencyApiProperties(

        @NotBlank
        String baseUrl
) {
}

Config:

@Configuration
@EnableConfigurationProperties(CurrencyApiProperties.class)
public class HttpClientConfig {

    @Bean
    public RestClient currencyRestClient(CurrencyApiProperties properties) {
        return RestClient.builder()
                .baseUrl(properties.baseUrl())
                .build();
    }
}

Bu professionalroq:

URL kodda emas
local/dev/prod’da alohida config bo‘ladi
secret env orqali beriladi

20. Authorization header qo‘shish

Ko‘p tashqi API token talab qiladi.

RestClient bilan

@Bean
public RestClient smsRestClient(SmsApiProperties properties) {
    return RestClient.builder()
            .baseUrl(properties.baseUrl())
            .defaultHeader("Authorization", "Bearer " + properties.token())
            .build();
}

WebClient bilan

@Bean
public WebClient smsWebClient(SmsApiProperties properties) {
    return WebClient.builder()
            .baseUrl(properties.baseUrl())
            .defaultHeader("Authorization", "Bearer " + properties.token())
            .build();
}

21. Timeout har doim bo‘lishi kerak

REST client yozishda eng muhim production qoidalardan biri:

Har bir external API call timeout bilan ishlasin.

Nega?

Agar tashqi SMS servis javob bermasa:

Sizning thread kutib qoladi
Requestlar yig‘iladi
Connection pool to‘ladi
API sekinlashadi
Oxiri service down bo‘lishi mumkin

Timeoutlar:

connect timeout
read/response timeout

Default uchun oddiy qiymatlar:

connect timeout: 1-3 sekund
read timeout: 3-10 sekund

Lekin real qiymat biznesga bog‘liq.


22. Retry nima?

Retry - vaqtinchalik xato bo‘lsa requestni qayta urinish.

Masalan:

1-urinish → network timeout
2-urinish → 503
3-urinish → success

Spring Retry loyihasi Spring applicationlarda declarative retry support berishini rasmiy repository ko‘rsatadi. (GitHub)


Retry qachon kerak?

Retry foydali:

Network glitch
503 Service Unavailable
504 Gateway Timeout
Temporary timeout
Rate limitdan keyin qisqa kutish

Retry xavfli:

Payment charge qilish
Order yaratish
Pul yechish
Duplicate yaratadigan POST request

Muhim qoida:

Retry faqat idempotent yoki xavfsiz operationlarda ishlatilsin.

23. Idempotent nima?

Idempotent operation - bir necha marta chaqirilsa ham natija buzilmaydi.

Odatda:

GET → idempotent
PUT → idempotent bo‘lishi mumkin
DELETE → odatda idempotent bo‘lishi mumkin
POST → ko‘pincha idempotent emas

Masalan:

GET /currency/USD/UZS

Buni retry qilish mumkin.

Lekin:

POST /payments/charge

retry qilinsa, mijozdan ikki marta pul yechilishi mumkin.

Bunday holatda idempotency key kerak bo‘ladi.


24. Spring Retry dependency

Gradle:

implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework.boot:spring-boot-starter-aop'

Maven:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Enable:

import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;

@Configuration
@EnableRetry
public class RetryConfig {
}

25. @Retryable

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;

@Service
public class CurrencyService {

    private final CurrencyApiClient currencyApiClient;

    public CurrencyService(CurrencyApiClient currencyApiClient) {
        this.currencyApiClient = currencyApiClient;
    }

    @Retryable(
            retryFor = ExternalServiceException.class,
            maxAttempts = 3,
            backoff = @Backoff(delay = 1000)
    )
    public CurrencyRateResponse getUsdToUzsRate() {
        return currencyApiClient.getRate("USD", "UZS");
    }
}

Ma’nosi:

ExternalServiceException chiqsa retry qil
Maksimal 3 marta urinsin
Har urinish orasida 1 sekund kutadi

@Recover

Agar retrylar ham muvaffaqiyatsiz bo‘lsa:

import org.springframework.retry.annotation.Recover;

@Recover
public CurrencyRateResponse recover(ExternalServiceException ex) {
    throw new ExternalServiceException("Currency API vaqtincha ishlamayapti");
}

Yoki fallback qiymat qaytarish mumkin, lekin ehtiyot bo‘ling:

@Recover
public CurrencyRateResponse recover(ExternalServiceException ex) {
    return new CurrencyRateResponse(
            "USD",
            "UZS",
            BigDecimal.valueOf(12600)
    );
}

Bu biznesga bog‘liq. Moliyaviy kurslarda eski qiymat qaytarish xavfli bo‘lishi mumkin.


26. Retry’ni haddan oshirmang

Yomon:

maxAttempts = 10
delay = 0

Bu tashqi servisga yana ko‘proq bosim beradi.

Yaxshi:

maxAttempts = 2 yoki 3
delay = 500ms / 1s
exponential backoff

Retry sababli umumiy request latency ham oshadi.

Masalan:

timeout 5s
retry 3 marta
jami kutish 15s+ bo‘lishi mumkin

Shuning uchun timeout va retry birga o‘ylanishi kerak.


27. External API response’ni ichki API response’dan ajrating

Tashqi API DTO’sini ichki response sifatida qaytarmang.

Yomon:

@GetMapping("/rate")
public CurrencyRateResponse getRate() {
    return currencyClient.getUsdToUzsRate();
}

Yaxshiroq:

public record ProductPriceResponse(
        Long productId,
        BigDecimal priceUsd,
        BigDecimal priceUzs
) {
}

Service:

public ProductPriceResponse getProductPriceInUzs(Long productId) {
    Product product = productRepository.findById(productId)
            .orElseThrow(() -> new NotFoundException("Product topilmadi"));

    CurrencyRateResponse rate = currencyClient.getUsdToUzsRate();

    BigDecimal priceUzs = product.getPriceUsd().multiply(rate.rate());

    return new ProductPriceResponse(
            product.getId(),
            product.getPriceUsd(),
            priceUzs
    );
}

Sabab:

External API o‘zgarsa, sizning public API buzilmasin.

28. ExternalServiceException handler

Oldingi exception handling darsiga qo‘shamiz.

public class ExternalServiceException extends RuntimeException {

    public ExternalServiceException(String message) {
        super(message);
    }
}

Global handler:

@ExceptionHandler(ExternalServiceException.class)
public ResponseEntity<ErrorResponse> handleExternalService(
        ExternalServiceException ex,
        HttpServletRequest request
) {
    ErrorResponse response = new ErrorResponse(
            "EXTERNAL_SERVICE_ERROR",
            ex.getMessage(),
            HttpStatus.BAD_GATEWAY.value(),
            request.getRequestURI(),
            LocalDateTime.now(),
            null
    );

    return ResponseEntity
            .status(HttpStatus.BAD_GATEWAY)
            .body(response);
}

Nega 502 Bad Gateway?

Chunki sizning API ishlayapti, lekin upstream/tashqi service bilan muammo bor.


29. Logging

External call xatolarini log qilish kerak.

private static final Logger log =
        LoggerFactory.getLogger(CurrencyClient.class);

public CurrencyRateResponse getUsdToUzsRate() {
    try {
        return restClient.get()
                .uri("/currency/USD/UZS")
                .retrieve()
                .body(CurrencyRateResponse.class);
    } catch (Exception ex) {
        log.error("Currency API call failed", ex);
        throw new ExternalServiceException("Currency API vaqtincha ishlamayapti");
    }
}

Lekin token, password, secretlarni log qilmang.

Yomon:

log.info("SMS token: {}", token);

Mutlaqo qilinmaydi.


30. Observability uchun nima log qilish kerak?

Yaxshi log:

external service nomi
endpoint nomi
status code
duration
correlation id / trace id

Yomon log:

token
password
full card number
secret key
shaxsiy ma’lumotlar

31. Product API bilan to‘liq misol

application.yml

external:
  currency:
    base-url: https://api.example.com
    connect-timeout-ms: 2000
    read-timeout-ms: 5000

Properties

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "external.currency")
public record CurrencyApiProperties(

        @NotBlank
        String baseUrl,

        @Min(100)
        int connectTimeoutMs,

        @Min(100)
        int readTimeoutMs
) {
}

RestClient config

@Configuration
@EnableConfigurationProperties(CurrencyApiProperties.class)
public class CurrencyClientConfig {

    @Bean
    public RestClient currencyRestClient(CurrencyApiProperties properties) {
        return RestClient.builder()
                .baseUrl(properties.baseUrl())
                .build();
    }
}

Interface

public interface CurrencyClient {

    CurrencyRateResponse getRate(String base, String target);
}

Implementation

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class CurrencyRestClientImpl implements CurrencyClient {

    private final RestClient restClient;

    public CurrencyRestClientImpl(RestClient currencyRestClient) {
        this.restClient = currencyRestClient;
    }

    @Override
    public CurrencyRateResponse getRate(String base, String target) {
        try {
            return restClient.get()
                    .uri("/currency/{base}/{target}", base, target)
                    .retrieve()
                    .body(CurrencyRateResponse.class);
        } catch (Exception ex) {
            throw new ExternalServiceException("Currency API vaqtincha ishlamayapti");
        }
    }
}

Product service

@Service
public class ProductPriceService {

    private final ProductRepository productRepository;
    private final CurrencyClient currencyClient;

    public ProductPriceService(ProductRepository productRepository,
                               CurrencyClient currencyClient) {
        this.productRepository = productRepository;
        this.currencyClient = currencyClient;
    }

    @Transactional(readOnly = true)
    public ProductPriceResponse getPriceInUzs(Long productId) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new NotFoundException("Product topilmadi"));

        CurrencyRateResponse rate = currencyClient.getRate("USD", "UZS");

        BigDecimal priceUzs = product.getPriceUsd().multiply(rate.rate());

        return new ProductPriceResponse(
                product.getId(),
                product.getPriceUsd(),
                priceUzs
        );
    }
}

32. Testing: tashqi API’ni real chaqirmang

Unit testda haqiqiy SMS yoki currency API chaqirish yomon.

Nega?

Test sekinlashadi
Internet kerak bo‘ladi
External API limit tugashi mumkin
Pullik SMS ketib qolishi mumkin
Test beqaror bo‘ladi

Yaxshiroq:

CurrencyClient mock qilinadi
WireMock ishlatiladi
MockWebServer ishlatiladi

Boshlanishiga kamida interface orqali ajratishni biling:

public interface CurrencyClient {
    CurrencyRateResponse getRate(String base, String target);
}

Testda fake implementation berish mumkin.


33. Ko‘p uchraydigan xatolar

Xato 1: Timeout qo‘ymaslik

Yomon:

new RestTemplate()

Yaxshi:

builder
    .connectTimeout(Duration.ofSeconds(2))
    .readTimeout(Duration.ofSeconds(5))
    .build()

Xato 2: Tokenni hardcode qilish

Yomon:

.header("Authorization", "Bearer abc123")

Yaxshi:

sms:
  token: ${SMS_TOKEN}

Xato 3: Har joyda client yaratish

Yomon:

public void send() {
    RestTemplate restTemplate = new RestTemplate();
}

Yaxshi:

@Bean
public RestTemplate restTemplate() {}

Yoki:

@Bean
public RestClient restClient() {}

Xato 4: External API DTO’sini domain modeli qilib ishlatish

Yomon:

ProductService external API response’ga to‘liq bog‘lanib qoladi

Yaxshi:

External DTO → Client layer
Internal DTO/domain → Service/API layer

Xato 5: Har bir xatoda retry qilish

Yomon:

400 Bad Request bo‘lsa ham retry
401 Unauthorized bo‘lsa ham retry
403 Forbidden bo‘lsa ham retry

Bular retry bilan tuzalmaydi.

Retry ko‘proq:

timeout
503
temporary network error

uchun.


Xato 6: Payment POST requestni retry qilish

Bu juda xavfli bo‘lishi mumkin.

1-request pul yechdi, lekin response kelmadi
retry yana pul yechdi

Bunday holatda idempotency key ishlatiladi.


34. Java developer uchun qisqa qoida

RestTemplate → legacy blocking client
RestClient → modern blocking client
WebClient → reactive/non-blocking client
HttpInterface → declarative client
Timeout → majburiy
Retry → faqat vaqtinchalik va xavfsiz xatolarda
ExternalServiceException → tashqi servis xatolari uchun
Config → base URL va token kodda emas

35. Amaliy vazifa

Oldingi Product CRUD API loyihasiga external currency client qo‘shing.

Talablar

Config

external:
  currency:
    base-url: https://api.example.com
    connect-timeout-ms: 2000
    read-timeout-ms: 5000

DTO

public record CurrencyRateResponse(
        String base,
        String target,
        BigDecimal rate
) {
}
public record ProductPriceResponse(
        Long productId,
        BigDecimal priceUsd,
        BigDecimal priceUzs
) {
}

Client

CurrencyClient interface
CurrencyRestClientImpl implementation

Service

ProductPriceService productni topadi
CurrencyClient orqali USD→UZS kurs oladi
priceUzs hisoblaydi

Exception

ExternalServiceException
GlobalExceptionHandler → 502 Bad Gateway

Muhim qoida

Currency API ishlamasa, clientga 500 emas, 502 qaytaring.
Timeout qo‘ying.
Base URL configdan olinsin.

Xulosa

REST client & HTTP mavzusida Java developer quyidagilarni bilishi kerak:

HTTP methodlar va status codelar
RestTemplate
WebClient
RestClient
HttpInterface
GET / POST request yuborish
Header qo‘shish
Bearer token yuborish
Timeout config
Retry config
ExternalServiceException
502 Bad Gateway
External DTO va internal DTO ajratish

Eng muhim professional qoida:

Tashqi API chaqiruvi hech qachon timeout, error handling va configsiz yozilmaydi.