Skip to content
Spring Boot sb microservices 3 min read

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: 8761 is 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

AspectClient-side (Eureka + LoadBalancer)Server-side (gateway/LB resolves)
Who picks the instanceThe callerA central proxy / gateway
Extra network hopNoYes (through the proxy)
Registry couplingClients know the registryHidden behind the proxy
Typical Spring useService-to-service callsEdge 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.

Last updated June 13, 2026
Was this helpful?