Service Discovery (Eureka)
In a fleet where service instances start, stop, and move across hosts, hard-coding http://10.0.3.7:8081 is hopeless. Service discovery lets services register themselves under a logical name and look each other up dynamically. This page sets up a Netflix Eureka registry, registers clients, and shows client-side discovery — with a note on Consul as an alternative.
Why discovery
Container orchestration assigns dynamic hosts and ports, and instances scale up and down constantly. A service registry is a phone book that stays current:
register: "orders-service" → 10.0.3.7:8081, 10.0.3.9:8081
lookup: "orders-service" → [10.0.3.7:8081, 10.0.3.9:8081] (healthy only)
Callers ask for orders-service by name and get the live set of healthy instances — no static config, no redeploy when instances change.
The Eureka server
Create a standalone Spring Boot app for the registry.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
# application.yml — the server itself should not register with or fetch from itself
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
Start it and the Eureka dashboard is available at http://localhost:8761.
Note:
8761is the conventional Eureka port. The dashboard lists every registered application and its instances — invaluable for debugging.
Registering a client
Any service that wants to be discoverable adds the client starter.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
# orders-service application.yml
spring:
application:
name: orders-service # the logical name it registers under
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true # register by IP, useful in containers
That’s it — no annotation is required. Since Spring Cloud added discovery auto-configuration, simply having a discovery client on the classpath enables registration. (@EnableDiscoveryClient still works and documents intent, but is optional.)
On startup the service POSTs its name, host, and port to Eureka, then sends a heartbeat every 30 seconds. Miss enough heartbeats and Eureka evicts the instance.
Output (Eureka server log):
Registered instance ORDERS-SERVICE/10.0.3.7:orders-service:8081 with status UP
Registered instance INVENTORY-SERVICE/10.0.3.9:inventory-service:8082 with status UP
Fetching the registry and client-side discovery
Clients also fetch the registry (cached and refreshed periodically) so they can resolve names locally. The simplest programmatic access is the DiscoveryClient:
@Service
@RequiredArgsConstructor
public class InventoryLocator {
private final DiscoveryClient discoveryClient;
public List<String> inventoryUris() {
return discoveryClient.getInstances("inventory-service").stream()
.map(si -> si.getUri().toString())
.toList();
}
}
In practice you rarely call DiscoveryClient directly. Client-side discovery shines when combined with Load Balancing: a @LoadBalanced RestClient or an OpenFeign client resolves a logical name and picks an instance for you.
// the lb:// scheme tells Spring Cloud LoadBalancer to resolve via discovery
InventoryResponse resp = restClient.get()
.uri("http://inventory-service/inventory/{sku}", sku) // name, not host
.retrieve()
.body(InventoryResponse.class);
client registry
│ resolve "inventory-service" ───────►│
│◄── [10.0.3.9:8082, 10.0.3.11:8082] ──┘
│ pick one (round-robin) → call it
Server-side vs client-side discovery
| Aspect | Client-side (Eureka + LoadBalancer) | Server-side (gateway/LB resolves) |
|---|---|---|
| Who picks the instance | The caller | A central proxy / gateway |
| Extra network hop | No | Yes (through the proxy) |
| Registry coupling | Clients know the registry | Hidden behind the proxy |
| Typical Spring use | Service-to-service calls | Edge via API Gateway |
Alternatives: Consul
Eureka is the classic Spring Cloud choice, but HashiCorp Consul is a strong alternative that adds health checking and a key-value store (usable for config too).
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
spring:
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true
Because Spring Cloud abstracts discovery behind DiscoveryClient, swapping Eureka for Consul is mostly a dependency and config change — your lb:// calls are unaffected. Kubernetes users often skip both and use the platform’s native DNS-based discovery via spring-cloud-kubernetes.
Tip: For high availability run Eureka as a peer-aware cluster (each server registers with the others) so the registry itself isn’t a single point of failure.