Back to Service Discovery
Q1Question 1 of 3

The difference between a Service Registry and Service Discovery

Key Concepts
  • A Service Registry is the database that stores the live list of service instances and their network addresses.
  • Service Discovery is the process of querying the registry to resolve a logical service name into a healthy endpoint.
  • Client-side discovery means the caller queries the registry and picks an instance itself using a load-balancing rule.
  • Server-side discovery means a load balancer or gateway consults the registry on the caller's behalf.
  • Health checks and heartbeats keep the registry accurate by pruning dead instances automatically.
  • Eureka favors availability over consistency, returning possibly-stale data rather than refusing, which suits discovery.
  • You cannot have discovery without a registry, and a registry alone does nothing until some discovery process queries it.

Definition

The Service Registry is the live database of service instances and their addresses, and Service Discovery is the process of querying that registry to resolve a service name into a healthy instance to call.
  • Service Registry – A component that stores the authoritative, live list of which service instances exist, their network addresses, and their health status.
  • Service Discovery – The process and mechanism by which a client finds a healthy instance of a service it wants to call by consulting the registry.
  • Registration – Each instance registers itself on startup with its logical name, host, port, and metadata, then sends periodic heartbeats.
  • Heartbeat and eviction – If the registry misses several heartbeats from an instance, it evicts that entry so stale addresses are never returned.
  • Client-side vs server-side – Discovery can happen in the caller (client-side) or in an intermediary like a gateway or load balancer (server-side).

Imagine National Bank of India has many loan officers scattered across branches, and they change desks constantly. The Service Registry is the central directory book at reception that always lists exactly which loan officers are on duty right now and at which desk. Service Discovery is what a customer like Rajesh actually does when he asks reception for an available loan officer and reception looks in the directory and points him to a free one. The directory book is the registry; the act of asking and being pointed is discovery.

Understanding the Concepts

What is a Service Registry?

A Service Registry is a runtime database that stores the current list of service instances, their network addresses, and their health. Every time a microservice instance starts, it registers itself by telling the registry its logical service name, its host and port, and often metadata like version or zone. It then sends periodic heartbeats to prove it is alive.

JAVA
package com.bank.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class RegistryServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RegistryServerApplication.class, args);
        System.out.println("Service Registry started on port 8761");
    }
}
TEXT
Expected output:
Service Registry started on port 8761

Examples of registries include Netflix Eureka, HashiCorp Consul, Apache ZooKeeper, etcd, and the built-in mechanism behind Kubernetes Services.

What is Service Discovery?

Service Discovery is the process of resolving a logical service name into a concrete, healthy network address at runtime. It is the activity that consumes the registry's data. Without discovery, a caller would need to hardcode downstream addresses, which breaks the moment instances scale, restart, or move.

JAVA
package com.bank.service;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class TransferService {

    private final DiscoveryClient discoveryClient;

    public TransferService(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    public String getPaymentServiceUrl() {
        List<ServiceInstance> instances = discoveryClient.getInstances("payment-service");
        if (instances.isEmpty()) {
            throw new IllegalStateException("No healthy payment-service instance found");
        }
        ServiceInstance instance = instances.get(0);
        return instance.getUri().toString();
    }
}
TEXT
Expected output:
http://10.0.3.14:8080

Why do we need both?

In microservices, instances are dynamic: they scale up during month-end salary processing at National Bank of India, scale down afterward, restart on new hosts, and fail unexpectedly. Hardcoding addresses is impossible because addresses change constantly. The registry provides the live directory, and discovery provides the lookup mechanism. One is the data store; the other is the query.

JAVA
package com.bank.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PaymentServiceApplication.class, args);
        // This instance registers itself with the registry on startup
        System.out.println("Payment Service registered with Eureka");
    }
}
TEXT
Expected output:
Payment Service registered with Eureka

How does client-side discovery differ from server-side discovery?

In client-side discovery, the calling service queries the registry, gets the list of healthy instances, and picks one using a load-balancing rule. Spring Cloud LoadBalancer works this way. In server-side discovery, the client calls a stable address like a load balancer or a Kubernetes Service, and that intermediary resolves the name.

| Aspect | Client-Side Discovery | Server-Side Discovery |
|---|---|---|
| Who queries the registry | The calling service itself | A load balancer or gateway |
| Load-balancing logic | Inside the caller | Inside the intermediary |
| Example tools | Spring Cloud LoadBalancer, Ribbon | Kubernetes Service, AWS ALB |
| Caller complexity | Higher, carries discovery logic | Lower, just calls a stable address |
| Network hops | Fewer, direct call to instance | One extra hop through the intermediary |

JAVA
package com.bank.service;

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.math.BigDecimal;

@Service
public class AccountService {

    private final WebClient.Builder webClientBuilder;

    public AccountService(WebClient.Builder webClientBuilder) {
        this.webClientBuilder = webClientBuilder;
    }

    public String settleTransfer(String accountId, BigDecimal amount) {
        // Client-side discovery: "lb://" triggers the load balancer to resolve
        return webClientBuilder.build()
                .post()
                .uri("lb://payment-service/api/v1/settle")
                .bodyValue(new SettleRequest(accountId, amount))
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }

    record SettleRequest(String accountId, BigDecimal amount) {}
}
TEXT
Expected output:
Settlement confirmed for account SAV2001

How do health checks keep the registry accurate?

Health checks are what prevent discovery from handing out dead addresses. A liveness check answers whether the process is alive, and a readiness check answers whether it can accept traffic. A service that is alive but still warming its connection pool should report not-ready so no requests are routed to it.

YAML
# application.yml for a payment-service instance
eureka:
  instance:
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
JAVA
package com.bank.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class DatabaseHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        boolean dbReachable = checkDatabaseConnection();
        if (dbReachable) {
            return Health.up().withDetail("database", "National Bank of India core DB reachable").build();
        }
        return Health.down().withDetail("database", "Core DB unreachable").build();
    }

    private boolean checkDatabaseConnection() {
        // Simulated check
        return true;
    }
}
TEXT
Expected output:
{"status":"UP","details":{"database":"National Bank of India core DB reachable"}}

What happens when an instance dies?

When an instance crashes, it stops sending heartbeats. After a configured number of missed heartbeats, the registry evicts the entry. Discovery then stops returning that address. There is a small staleness window between the crash and the eviction, which is why discovery is always combined with retries and circuit breakers.

JAVA
package com.bank.resilience;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.math.BigDecimal;

@Service
public class ResilientTransferService {

    private final WebClient.Builder webClientBuilder;

    public ResilientTransferService(WebClient.Builder webClientBuilder) {
        this.webClientBuilder = webClientBuilder;
    }

    @CircuitBreaker(name = "payment", fallbackMethod = "fallbackSettle")
    public String settle(String accountId, BigDecimal amount) {
        return webClientBuilder.build()
                .post()
                .uri("lb://payment-service/api/v1/settle")
                .bodyValue(new SettleRequest(accountId, amount))
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }

    public String fallbackSettle(String accountId, BigDecimal amount, Throwable t) {
        return "Settlement queued for retry: " + accountId;
    }

    record SettleRequest(String accountId, BigDecimal amount) {}
}
TEXT
Expected output (when payment-service is down):
Settlement queued for retry: SAV2001

How does the API Gateway use discovery?

The API Gateway is a consumer of discovery. It resolves backend services by their logical names so it can route incoming requests. Spring Cloud Gateway integrates with Eureka and uses the lb:// scheme to resolve routes dynamically.

YAML
# Gateway route configuration
spring:
  cloud:
    gateway:
      routes:
        - id: payment-route
          uri: lb://payment-service
          predicates:
            - Path=/api/v1/payments/**
        - id: account-route
          uri: lb://account-service
          predicates:
            - Path=/api/v1/accounts/**
TEXT
Gateway resolves payment-service to 10.0.3.14:8080 via Eureka
Gateway resolves account-service to 10.0.3.15:8081 via Eureka

What consistency trade-offs do registries make?

Registries like Eureka favor availability over consistency (AP in the CAP theorem). When a network partition occurs, Eureka continues serving the last known list rather than refusing, which is the right choice for discovery because approximate freshness combined with retries is better than no answer at all. Consul and etcd lean toward consistency (CP), which suits different use cases like configuration storage.

| Registry | Consistency Model | Best For |
|---|---|---|
| Eureka | AP (available, partition-tolerant) | Service discovery where staleness is tolerable |
| Consul | CP (consistent, partition-tolerant) | Discovery plus configuration and KV storage |
| etcd | CP (strongly consistent) | Kubernetes backing store, leader election |
| Kubernetes DNS | Server-side, built-in | Pod-to-pod discovery inside a cluster |

JAVA
package com.bank.config;

// Eureka self-preservation mode: if too many instances miss heartbeats at once,
// Eureka assumes a network partition rather than evicting them all.
// This is an AP trade-off: better to serve stale data than no data.

// eureka.server.enable-self-preservation=true  (default)
// eureka.server.renewal-percent-threshold=0.85

What should a senior engineer know?

A senior engineer understands that the registry is a trust anchor. Registration should be authenticated to prevent registry poisoning, where a rogue instance registers and hijacks traffic. In production, combine discovery with mutual TLS so the caller also verifies the instance's identity. Monitor registry size, heartbeat rates, and eviction counts as operational metrics.

JAVA
package com.bank.metrics;

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class DiscoveryMetrics {

    private final DiscoveryClient discoveryClient;
    private final MeterRegistry meterRegistry;

    public DiscoveryMetrics(DiscoveryClient discoveryClient, MeterRegistry meterRegistry) {
        this.discoveryClient = discoveryClient;
        this.meterRegistry = meterRegistry;
    }

    @Scheduled(fixedRate = 30000)
    public void recordInstanceCounts() {
        int paymentInstances = discoveryClient.getInstances("payment-service").size();
        meterRegistry.gauge("discovery.instances.payment", paymentInstances);
        System.out.println("Payment service instances: " + paymentInstances);
    }
}
TEXT
Expected output:
Payment service instances: 3

Visual Overview

DIAGRAM
                    Service Registry (Eureka / Consul)
              ┌─────────────────────────────────────────┐
              │  payment-service:                        │
              │    instance-1  10.0.3.14:8080  HEALTHY   │
              │    instance-2  10.0.3.15:8080  HEALTHY   │
              │    instance-3  10.0.3.16:8080  DOWN      │
              │  account-service:                        │
              │    instance-1  10.0.4.10:8081  HEALTHY   │
              └─────────────┬───────────────────────────┘
                            │
          ┌─────────────────┼─────────────────┐
          │ Register +      │ Query healthy    │
          │ Heartbeat       │ instances        │
          ▼                 ▼                  ▼
   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
   │ payment-svc  │  │ account-svc  │  │  API Gateway │
   │ instance-1   │  │ (caller)     │  │ (routes via  │
   │              │  │ Client-side  │  │  discovery)  │
   └──────────────┘  │ discovery    │  └──────────────┘
                     └──────┬───────┘
                            │ Direct call
                            ▼
                     ┌──────────────┐
                     │ payment-svc  │
                     │ instance-2   │
                     │ (picked by   │
                     │  load balance│
                     └──────────────┘

Key Takeaway

A Service Registry and Service Discovery are related but distinct. The registry is a thing and discovery is an activity. The Service Registry is the authoritative, live directory that stores which instances of each service exist, their network addresses, and their health. Service Discovery is the process by which a caller resolves a logical service name into a concrete, healthy instance by consulting that registry. You cannot have discovery without a registry, and a registry by itself does nothing useful until some discovery process queries it.

At National Bank of India, Pankaj designed the payment service to run several instances that scale up during month-end salary processing and scale down afterward. Each instance registers itself with Eureka on startup, sending its name, address, and health, then sends periodic heartbeats. When the account service needs to call the payment service, it uses Spring Cloud LoadBalancer to query the registry and pick a healthy instance. When an instance dies, the registry evicts it after missed heartbeats, and discovery immediately stops routing traffic there.

The two discovery flavors matter in production. Client-side discovery means the caller itself queries the registry and picks an instance, which is what Spring Cloud LoadBalancer does with the lb:// scheme. Server-side discovery means a stable intermediary like a Kubernetes Service or an API Gateway consults the registry on the caller's behalf. Either way, the gateway uses discovery to resolve backends, and registries make deliberate consistency trade-offs: Eureka favors availability and may return slightly stale data, which is fine for discovery when combined with retries.

Remember one sentence: the registry is the live source of truth about instances, and discovery is the glue that turns a logical service name into a real, reachable, healthy endpoint.

---

Share
Use ← → arrow keys to navigate