JAVA + SPRING AI — INTERMEDIATE
How Microservices Talk: RestClient, Service Discovery & API Gateway
The Introduction level covered whya system gets split into several independent Spring Boot services. Independent services are only useful if they can still cooperate — so here’s how one service actually calls another, how it finds that other service without hardcoding an address, and how a single front door routes every incoming request to the right place.
The Quick Answer
One service calls another the same way any HTTP client calls any REST API — Spring’s current guidance is to use RestClient for a synchronous call like this, reaching for WebClient only if the call needs to be reactive or stream data. Service discovery (Eureka is the classic Spring Cloud option) solves the problem of where to send that call: a service registers itself by name, and every other service can look it up by that name instead of hardcoding a specific host and port that could change the moment that service redeploys. And an API gateway (Spring Cloud Gateway) gives outside clients one single address to talk to, routing each incoming request to whichever backend service should actually handle it.
Service Topology at a Glance
Here’s the shape this whole level is building toward — a client only ever talks to the gateway; every hop after that is one service calling another directly:
Calling One Service From Another: RestClient
Spring’s own reference documentation and its official blog on HTTP clients are direct about which client to reach for today: pick RestClient for most synchronous cases, and WebClient specifically when the application needs reactive APIs or streaming. RestTemplate — the client shown in a lot of older tutorials — is on a real, published path out: Spring announced its intent to deprecate RestTemplate starting in Spring Framework 7.0, with formal @Deprecated marking planned for 7.1 and full removal planned for Spring Framework 8.0 — so this pillar teaches RestClient as the forward-looking default, not RestTemplate.
Here’s Order Service making a real synchronous call to Inventory Service to check stock before confirming an order:
public record StockLevel(String sku, int quantityAvailable) {}
@Service
public class InventoryClient {
private final RestClient restClient;
// The base URL uses the logical service name, "inventory-service" —
// not a hardcoded host/port. Service discovery (covered next)
// resolves that name to a real address at request time.
public InventoryClient(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder
.baseUrl("http://inventory-service")
.build();
}
public StockLevel checkStock(String sku) {
return restClient.get()
.uri("/inventory/{sku}", sku)
.retrieve()
.body(StockLevel.class);
}
}
@Service
public class OrderService {
private final InventoryClient inventoryClient;
public OrderService(InventoryClient inventoryClient) {
this.inventoryClient = inventoryClient;
}
public void placeOrder(String sku) {
StockLevel stock = inventoryClient.checkStock(sku);
if (stock.quantityAvailable() < 1) {
throw new OutOfStockException(sku);
}
// ...continue placing the order
}
}Notice InventoryClient is constructor-injected into OrderService — the exact same dependency injection mechanics from the very first topic in this pillar. The only genuinely new idea here is what that base URL points at.
Service Discovery: Finding a Service By Name
http://inventory-service in the code above isn’t a real hostname — it’s a logical service name. Something needs to translate that name into Inventory Service’s actual, current network address, since that address can change every time the service restarts, redeploys, or scales up with another instance. That translation is the entire job of service discovery: every service registers itself with a shared discovery server under its name, and looks up other services by that same name instead of a fixed address.
Eureka, part of Spring Cloud Netflix, is the long-standing option for this in the Spring ecosystem, and it’s still current and actively documented — it hasn’t been placed into maintenance mode the way some other, older Spring Cloud Netflix components have. Setting up a basic Eureka server is itself a small, ordinary Spring Boot application:
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}Every other service — Order Service, Inventory Service, Payment Service — adds the spring-cloud-starter-netflix-eureka-client dependency and a small bit of configuration pointing at the discovery server’s address, and it automatically registers itself under its own service name (typically drawn from its spring.application.name property) at startup — the same “don’t configure it by hand, let the framework handle it” philosophy behind Spring Boot’s own auto-configuration.
It’s worth being honest that Eureka isn’t the only answer here anymore. A team deploying entirely on Kubernetes already has a working, built-in service discovery mechanism — Kubernetes’ own DNS-based service resolution — and often evaluates that before reaching for an additional, application-level tool like Eureka. Eureka still earns its place for teams that aren’t 100% Kubernetes-only, or that need service lookups to work consistently across a mix of environments. Either way, the underlying idea taught here — look a service up by name instead of hardcoding an address — is the important, durable part; which specific tool performs that lookup is a secondary, environment-dependent choice.
API Gateway: One Front Door
Service discovery solves how services find each other. It doesn’t solve a related but separate problem: an outside client — a mobile app, a browser — shouldn’t need to know that “placing an order” involves an Order Service, and “checking a delivery status” involves some other service entirely. An API gateway gives that outside client exactly one address to talk to, and takes on the job of routing each incoming request to whichever backend service should actually handle it. Spring Cloud Gatewayis the current Spring project for this. Its routes can be configured either declaratively, in YAML, or with a Java-based fluent routes API — both are current, supported approaches. Here’s the YAML shape, routing requests under /orders/** to Order Service by its logical service name:
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/orders/**
filters:
- StripPrefix=0The lb:// prefix (rather than a plain http://) tells the gateway to resolve order-service through service discovery too — the gateway itself is just another consumer of the exact same Eureka lookup mechanism every other service uses. A request to /orders/42arrives at the gateway, matches this route’s Path predicate, and gets forwarded straight to whichever Order Service instance is currently registered and healthy.
Try It: A Request Crossing Three Services
Step through a simulated “place an order” request as it hops from the gateway through Order Service, Inventory Service, and Payment Service — and try making one service fail partway through to see exactly where the chain breaks:
Fun Fact
Spring Cloud Netflix originally bundled several Netflix OSS components together — Eureka for discovery, but also Hystrix for fault-tolerance and Ribbon for client-side load balancing. Hystrix and Ribbon were placed into maintenance mode years ago and effectively superseded (Resilience4j is the modern fault-tolerance library commonly used in their place today), while Eureka itself kept going as the still-current, still-documented member of that original lineup.
Test Yourself
According to Spring’s own current guidance, what should a new Spring Boot service use for a synchronous HTTP call to another service?
What problem does service discovery (like Eureka) solve?
What does an API gateway like Spring Cloud Gateway do in a microservices architecture?
In the coordination demo on this page, what happens to the steps that already ran before a simulated failure?
Every hop in that demo assumed each service could just answer instantly and correctly. What happens to a service’s own dataonce there are several separate databases involved, and one service’s failure can leave another service’s data half-updated? Continue to the Advanced level →