Careeroza
Calling Another Service with RestClient
RestClient
RestClient (Spring Framework 6.1, Spring Boot 3.2+) is the modern, fluent, synchronous HTTP client. It replaces RestTemplate for new code. Spring Boot auto-configures a RestClient.Builder bean that already has Jackson (JSON) set up, so you inject the builder and set a base URL.
| Client | Style | Use it for |
|---|---|---|
RestTemplate | Old template methods | Existing code only (maintenance mode) |
RestClient | Fluent, blocking | New Spring MVC services (this course) |
WebClient | Fluent, reactive | Spring WebFlux / non-blocking apps |
| OpenFeign / HTTP Interface | Declarative interface | Medium level (Inter-service Communication) |
The Product Client in order-service
package com.shopease.order.client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.math.BigDecimal;
@Component
public class ProductClient {
private final RestClient restClient;
public ProductClient(RestClient.Builder builder,
@Value("${shopease.services.product-url}") String productUrl) {
this.restClient = builder.baseUrl(productUrl).build();
}
/** order-service's OWN copy of the response shape: only the fields it needs. */
public record ProductResponse(Long id, String name, BigDecimal price, int stock) {}
public record StockRequest(int quantity) {}
public ProductResponse getProduct(Long id) {
return restClient.get()
.uri("/api/products/{id}", id)
.retrieve()
.body(ProductResponse.class);
}
public ProductResponse reserve(Long id, int quantity) {
return restClient.post()
.uri("/api/products/{id}/reserve", id)
.body(new StockRequest(quantity))
.retrieve()
.body(ProductResponse.class);
}
}Note: order-service does not import a class from product-service. It defines its own ProductResponse record. Sharing a JAR of DTOs between services couples their releases; copying a small record keeps them independent.
Placing an Order
package com.shopease.order;
import com.shopease.order.client.ProductClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class OrderService {
private final ProductClient productClient;
private final OrderRepository orders;
public OrderService(ProductClient productClient, OrderRepository orders) {
this.productClient = productClient;
this.orders = orders;
}
public record ItemRequest(Long productId, int quantity) {}
public record PlaceOrderRequest(Long userId, List<ItemRequest> items) {}
@Transactional
public Order place(PlaceOrderRequest req) {
Order order = new Order(req.userId());
for (ItemRequest item : req.items()) {
// 1 HTTP call: reserve stock and get the current name/price
var product = productClient.reserve(item.productId(), item.quantity());
order.addItem(product.id(), product.name(), product.price(), item.quantity());
}
order.setStatus(OrderStatus.CREATED);
return orders.save(order); // stored in order_db only
}
}The Controller and the Result
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService service;
public OrderController(OrderService service) { this.service = service; }
@PostMapping
public ResponseEntity<OrderResponse> place(@RequestBody OrderService.PlaceOrderRequest req) {
Order saved = service.place(req);
return ResponseEntity.created(URI.create("/api/orders/" + saved.getId()))
.body(OrderResponse.from(saved));
}
}Example
With both services running:
curl -i -X POST localhost:8082/api/orders \
-H "Content-Type: application/json" \
-d '{"userId":7,"items":[{"productId":1,"quantity":2},{"productId":2,"quantity":1}]}'Example output
HTTP/1.1 201 Created
Location: /api/orders/1
Content-Type: application/json
{"id":1,"userId":7,"status":"CREATED","total":3897.00,
"items":[{"productId":1,"productName":"boAt Rockerz 450","unitPrice":1499.00,"quantity":2},
{"productId":2,"productName":"Prestige Kettle","unitPrice":899.00,"quantity":1}]}
# product-service log
POST /api/products/1/reserve 200
POST /api/products/2/reserve 200Practice
Exercise: Add a method listByCategory(String category) to ProductClient that calls GET /api/products?category={category} and returns a List<ProductResponse>.
Show answer
import org.springframework.core.ParameterizedTypeReference;
public List<ProductResponse> listByCategory(String category) {
return restClient.get()
.uri(uri -> uri.path("/api/products")
.queryParam("category", category)
.build())
.retrieve()
.body(new ParameterizedTypeReference<List<ProductResponse>>() {});
}