← Назад к вопросам

В чем разница между WebClient и RestTemplate?

1.7 Middle🔥 241 комментариев
#REST API и микросервисы#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

В чем разница между WebClient и RestTemplate?

WebClient и RestTemplate — это два разных подхода к HTTP запросам в Spring. RestTemplate — синхронный, старый подход. WebClient — асинхронный, реактивный, современный подход. WebClient является рекомендуемым решением начиная с Spring 5.

RestTemplate: синхронный подход (legacy)

RestTemplate блокирует поток до ответа сервера

@Service
public class UserServiceWithRestTemplate {
    private final RestTemplate restTemplate;
    
    public UserServiceWithRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
    
    // Синхронный запрос
    public User getUser(String id) {
        String url = "http://api.example.com/users/" + id;
        
        // БЛОКИРУЕТ поток пока не придет ответ!
        ResponseEntity<User> response = restTemplate.getForEntity(
            url,
            User.class
        );
        
        return response.getBody();
    }
    
    // POST запрос
    public User createUser(User user) {
        String url = "http://api.example.com/users";
        
        // БЛОКИРУЕТ!
        ResponseEntity<User> response = restTemplate.postForEntity(
            url,
            user,
            User.class
        );
        
        return response.getBody();
    }
}

// Использование
@RestController
public class UserController {
    @Autowired
    private UserServiceWithRestTemplate service;
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable String id) {
        // Поток ждет ответа от удаленного API
        // Если API отвечает 5 секунд — поток блокирован 5 секунд!
        return service.getUser(id);
    }
}

Проблема: Thread pool exhaustion

Томкат имеет 200 потоков по умолчанию

Если каждый запрос делает HTTP запрос на 5 секунд:
- 200 запросов × 5 сек = 1000 потоков нужно для обработки одновременно
- Но у нас только 200!
- После 200 запросов в очереди — все падает

Торчок = 5 сек × 200 запросов = 1000 запросов/сек максимум

WebClient: асинхронный подход (modern)

WebClient не блокирует поток, использует reactive streams

@Service
public class UserServiceWithWebClient {
    private final WebClient webClient;
    
    public UserServiceWithWebClient(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://api.example.com").build();
    }
    
    // Асинхронный запрос
    public Mono<User> getUser(String id) {
        return webClient
            .get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
        // НЕ блокирует! Возвращает Mono<User>
    }
    
    // POST запрос
    public Mono<User> createUser(User user) {
        return webClient
            .post()
            .uri("/users")
            .body(Mono.just(user), User.class)
            .retrieve()
            .bodyToMono(User.class);
        // НЕ блокирует!
    }
    
    // Множественные запросы параллельно
    public Mono<List<User>> getAllUsersParallel() {
        return Mono.zip(
            getUser("1"),
            getUser("2"),
            getUser("3"),
            (u1, u2, u3) -> List.of(u1, u2, u3)
        );
        // Три запроса идут параллельно, один поток!
    }
}

// Использование
@RestController
public class UserController {
    @Autowired
    private UserServiceWithWebClient service;
    
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        // Поток НЕ ждет ответа
        // Возвращает Mono, Spring подписывается на него
        // Когда приходит ответ — обработка продолжается
        return service.getUser(id);
    }
}

Сравнение

АспектRestTemplateWebClient
МодельСинхронная (blocking)Асинхронная (non-blocking)
СтильImperative (команды)Declarative (потоки данных)
ПотокБЛОКИРУЕТНЕ блокирует
МасштабируемостьДо 200 одновременныхТысячи одновременных
APIПростойБолее сложный (Mono/Flux)
ПроизводительностьНизкая при нагрузкеВысокая
СовместимостьОбычный кодТребует реактивного кода
Deprecation⚠️ Deprecated с Spring 5.3+✅ Рекомендуется

Практический пример: обработка множества запросов

С RestTemplate (проблема)

@Service
public class DataAggregationRestTemplate {
    private final RestTemplate restTemplate;
    
    public UserProfile getUserProfile(String userId) {
        // 4 синхронных запроса - поток блокирован!
        User user = getUser(userId);              // 500ms
        List<Post> posts = getPosts(userId);      // 800ms
        List<Comment> comments = getComments(userId); // 1000ms
        Settings settings = getSettings(userId);   // 200ms
        
        return new UserProfile(user, posts, comments, settings);
        // Итого: 500 + 800 + 1000 + 200 = 2500ms для одного запроса!
    }
    
    private User getUser(String id) {
        return restTemplate.getForObject("http://api/users/" + id, User.class);
    }
    
    private List<Post> getPosts(String userId) {
        return restTemplate.getForObject("http://api/posts?user=" + userId, List.class);
    }
    
    private List<Comment> getComments(String userId) {
        return restTemplate.getForObject("http://api/comments?user=" + userId, List.class);
    }
    
    private Settings getSettings(String userId) {
        return restTemplate.getForObject("http://api/settings/" + userId, Settings.class);
    }
}

С WebClient (оптимально)

@Service
public class DataAggregationWebClient {
    private final WebClient webClient;
    
    public Mono<UserProfile> getUserProfile(String userId) {
        // 4 асинхронных запроса - параллельно!
        Mono<User> user = getUser(userId);
        Mono<List<Post>> posts = getPosts(userId);
        Mono<List<Comment>> comments = getComments(userId);
        Mono<Settings> settings = getSettings(userId);
        
        return Mono.zip(user, posts, comments, settings)
            .map(tuple -> new UserProfile(
                tuple.getT1(),
                tuple.getT2(),
                tuple.getT3(),
                tuple.getT4()
            ));
        // Итого: max(500, 800, 1000, 200) = 1000ms для одного запроса!
        // Запросы идут параллельно!
    }
    
    private Mono<User> getUser(String id) {
        return webClient
            .get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }
    
    private Mono<List<Post>> getPosts(String userId) {
        return webClient
            .get()
            .uri("/posts?user={userId}", userId)
            .retrieve()
            .bodyToFlux(Post.class)
            .collectList();
    }
    
    private Mono<List<Comment>> getComments(String userId) {
        return webClient
            .get()
            .uri("/comments?user={userId}", userId)
            .retrieve()
            .bodyToFlux(Comment.class)
            .collectList();
    }
    
    private Mono<Settings> getSettings(String userId) {
        return webClient
            .get()
            .uri("/settings/{userId}", userId)
            .retrieve()
            .bodyToMono(Settings.class);
    }
}

Конфигурация WebClient

@Configuration
public class WebClientConfig {
    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder
            .baseUrl("http://api.example.com")
            .defaultHeader("Content-Type", "application/json")
            .defaultHeader("User-Agent", "MyApp/1.0")
            .filter((request, next) -> {
                // Логирование запроса
                System.out.println("Запрос: " + request.method + " " + request.url);
                return next.exchange(request)
                    .doOnNext(response -> {
                        System.out.println("Ответ: " + response.statusCode);
                    });
            })
            .build();
    }
}

Обработка ошибок

RestTemplate

try {
    ResponseEntity<User> response = restTemplate.getForEntity(
        "http://api/users/" + id,
        User.class
    );
    
    if (response.getStatusCode() == HttpStatus.OK) {
        return response.getBody();
    } else if (response.getStatusCode() == HttpStatus.NOT_FOUND) {
        throw new UserNotFoundException();
    }
} catch (RestClientException e) {
    throw new ServiceException("API error", e);
}

WebClient

return webClient
    .get()
    .uri("/users/{id}", id)
    .retrieve()
    .onStatus(
        HttpStatus::isError,
        response -> {
            if (response.statusCode.equals(HttpStatus.NOT_FOUND)) {
                return Mono.error(new UserNotFoundException());
            }
            return Mono.error(new ServiceException("API error"));
        }
    )
    .bodyToMono(User.class)
    .timeout(Duration.ofSeconds(5))
    .onErrorMap(TimeoutException.class, e -> new ServiceTimeoutException("API timeout", e));

Миграция с RestTemplate на WebClient

Пошаговая миграция

// Шаг 1: Добавить WebClient Bean
@Configuration
public class WebClientConfiguration {
    @Bean
    public WebClient webClient() {
        return WebClient.create("http://api.example.com");
    }
}

// Шаг 2: Заменить RestTemplate на WebClient постепенно
@Service
public class UserService {
    private final WebClient webClient;
    private final RestTemplate restTemplate;  // deprecated
    
    public Mono<User> getUser(String id) {
        // Новый метод
        return webClient
            .get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }
    
    @Deprecated
    public User getUserBlocking(String id) {
        // Старый метод
        return restTemplate.getForObject("/users/" + id, User.class);
    }
}

// Шаг 3: Обновить контроллеры на реактивные
@RestController
public class UserController {
    @Autowired
    private UserService service;
    
    // Реактивный endpoint
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return service.getUser(id);
    }
}

Когда использовать что

Используй RestTemplate когда:

  • Старый проект с обычным Java/Spring
  • Нет требований к высокой масштабируемости
  • Проект не требует реактивности
  • Простые блокирующие запросы

Используй WebClient когда:

  • Новый проект
  • Требуется высокая масштабируемость
  • Микросервисная архитектура
  • Нужна реактивность
  • Spring Boot 5.0+

Производительность

Одновременные запросы: 1000
Время ответа сервера: 1 сек
Поток томката: 200

RestTemplate:
- Нужно 1000 потоков
- 1000/200 = 5x перегрузка
- Много context switching
- Низкая throughput

WebClient (асинхронный):
- Нужно ~10 потоков для обработки
- Высокая throughput
- Минимум context switching
- Может обработать 10000+ параллельных запросов

Вывод

RestTemplate:

  • Старый, синхронный подход
  • Простой для понимания
  • ⚠️ Deprecated
  • Плохая масштабируемость

WebClient:

  • Новый, асинхронный подход
  • Требует понимания Mono/Flux
  • ✅ Рекомендуется Spring
  • Отличная масштабируемость

Рекомендация: начиная с Spring 5.0, всегда используй WebClient вместо RestTemplate. Это не опционально, а архитектурное требование для production приложений.