Запросы к другим API: RestClient
Иногда серверу самому нужно сходить в чужой API: узнать курс валют, проверить адрес, забрать данные. В учебнике вы делали такие запросы через java.net.http.HttpClient. В Spring для этого есть RestClient — клиент с цепочкой вызовов, который сам превращает JSON ответа в объект Java. Научим блог импортировать статью из тестового API JSONPlaceholder.
Подключение
В Spring Boot 4 клиент — отдельный стартер (на start.spring.io — HTTP Client):
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>Адрес чужого API вынесем в настройки:
# src/main/resources/application.yaml
blog:
import:
base-url: https://jsonplaceholder.typicode.comОтвет чужого API
Запрос https://jsonplaceholder.typicode.com/posts/1 возвращает JSON с полями userId, id, title и body. Опишем запись только с нужными полями — лишние Spring пропустит:
// src/main/java/com/example/blog/ExternalPost.java
package com.example.blog;
public record ExternalPost(Long id, String title, String body) {
}Сервис импорта
// src/main/java/com/example/blog/ImportService.java
package com.example.blog;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class ImportService {
private final RestClient restClient;
private final PostService postService;
public ImportService(RestClient.Builder builder,
@Value("${blog.import.base-url}") String baseUrl,
PostService postService) {
this.restClient = builder.baseUrl(baseUrl).build();
this.postService = postService;
}
public PostResponse importPost(Long externalId, String username) {
ExternalPost external = restClient.get()
.uri("/posts/{id}", externalId)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new IllegalArgumentException("Нет внешней статьи " + externalId);
})
.body(ExternalPost.class);
var request = new NewPostRequest(external.title(), external.body());
return postService.create(request, username);
}
}RestClient.Builder Spring Boot создаёт сам и уже настраивает в нём Jackson. uri("/posts/{id}", externalId) подставляет номер в адрес, retrieve() выполняет запрос, body(ExternalPost.class) превращает JSON в запись. onStatus перехватывает ответы 4xx и бросает наше исключение — его обработчик из главы об ошибках превратит в 400.
Адрес импорта
// src/main/java/com/example/blog/ImportController.java
@PostMapping("/api/import/{externalId}")
@ResponseStatus(HttpStatus.CREATED)
public PostResponse importPost(@PathVariable Long externalId,
Principal principal) {
return importService.importPost(externalId, principal.getName());
}Импорт доступен только вошедшим, поэтому запрос отправляем с токеном из урока о JWT:
curl -X POST http://localhost:8080/api/import/1 -H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:8080/api/import/9999 -H "Authorization: Bearer $TOKEN"{"id":65,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae ...","author":"anna","createdAt":"2026-10-04T09:10:31.243724400Z"}
{"detail":"Нет внешней статьи 9999","instance":"/api/import/9999","status":400,"title":"Bad Request"}Первая статья пришла из JSONPlaceholder (там тексты-заглушки на латыни) и сохранилась в блоге от имени Анны. На несуществующий номер чужой API ответил 404, и мы вернули клиенту понятное сообщение.
Другие методы
| Задача | Вызов |
|---|---|
| Отправить JSON | restClient.post().uri("/posts").body(request).retrieve() |
| Заголовок | .header("Authorization", "Bearer ...") |
| Ответ с кодом и заголовками | .retrieve().toEntity(ExternalPost.class) |
| Список | .body(new ParameterizedTypeReference<List<ExternalPost>>() {}) |
Частая ошибка
Без стартера spring-boot-starter-restclient приложение не запустится:
No qualifying bean of type 'org.springframework.web.client.RestClient$Builder' available: expected at least 1 bean which qualifies as autowire candidate.В Spring Boot 3 клиент входил в веб-стартер, а в Spring Boot 4 его подключают отдельно. Добавьте зависимость — и бин RestClient.Builder появится.