← Назад к вопросам
В чем разница между 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);
}
}
Сравнение
| Аспект | RestTemplate | WebClient |
|---|---|---|
| Модель | Синхронная (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 приложений.