Appearance
Service Discovery in Microservices | Eureka & its Spring Boot Implementation
The Hotel Front Desk Analogy
Imagine arriving in a large hotel with hundreds of rooms, each occupied by a different service. If you need room service, you do not wander the corridors knocking on random doors. You call the front desk, tell them what you need, and they look up the right room number for you. The front desk maintains a living registry of which guests are in which rooms, updating it as guests check in and check out.
Eureka is that front desk for your microservices. When a new instance of a service starts, it registers itself with Eureka (checks in). When it shuts down gracefully, it deregisters (checks out). Any service that needs to call another service asks Eureka for the current list of available instances — and Eureka provides a live, accurate directory.
Why Hardcoded URLs Break in Production
In the previous chapters, order service always called http://localhost:8082/product/{id}. This works when there is exactly one instance of product service running on a fixed host and port. In production, this assumption fails immediately:
| Problem | Consequence |
|---|---|
| Single point of failure | If the hardcoded instance goes down, all traffic fails |
| No load balancing | All traffic hits one instance even when 10 others are idle |
| Tight coupling | Moving or scaling product service requires changing order service config |
| Environment fragility | Dev, staging, and production all need different URLs managed manually |
The solution is service discovery: instead of knowing where a service is, you know what its name is. The discovery system tells you where to find it right now.
Eureka Architecture: Server and Client
Eureka Server (the Phone Book)
The server maintains an in memory registry — a Map<String, Lease<InstanceInfo>> where:
Key = "APP-NAME/INSTANCE-ID" (e.g., "PRODUCT-SERVICE/192.168.1.5:8082")
Value = Lease<InstanceInfo> {appName, hostName, ipAddress, port, status, lastRenewed, leaseDuration, ...}There is no database persistence. All data lives in memory. This is intentional: Eureka prioritizes availability over consistency (AP system in the CAP theorem sense).
Eureka Client Operations
Every Eureka client (your microservice) performs two operations:
- Register — at startup, sends its metadata (name, IP, port, health URL) to the server. The server creates an entry in its registry.
- Discover — fetches the registry (or a delta update) from the server and caches it locally. Uses the local cache to resolve service names to instances without calling the server on every request.
Setting Up the Eureka Server
pom.xml
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Application Class
java
@SpringBootApplication
@EnableEurekaServer // activates Eureka server beans: registry, dashboard, REST endpoints
public class ServiceDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDiscoveryApplication.class, args);
}
}application.properties
properties
server.port=8761
spring.application.name=service-discovery
# This IS the server — it should not register itself with itself
eureka.client.register-with-eureka=false
# This IS the server — it should not fetch the registry from itself
eureka.client.fetch-registry=falseStart the application and open http://localhost:8761. You will see the Eureka dashboard with "No instances currently registered" — that is expected.
Setting Up a Eureka Client (product-service)
pom.xml
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>application.properties
properties
server.port=8082
spring.application.name=product-service
# Register this service instance with Eureka (default: true)
eureka.client.register-with-eureka=true
# Fetch the registry so this service can discover others (default: true)
eureka.client.fetch-registry=true
# Tell the client where the Eureka server lives
eureka.client.service-url.defaultZone=http://localhost:8761/eurekadefaultZone is the critical URL. Without it, the client does not know which server to register with and will fail to start (or silently fail to register).
After starting product-service, refresh the Eureka dashboard: you should see PRODUCT-SERVICE listed with status UP.
Setting Up order-service as a Eureka Client
Apply the same dependency and configuration to order service:
properties
server.port=8081
spring.application.name=order-service
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://localhost:8761/eurekaAfter starting order service, both appear in the dashboard.
Calling product-service from order-service Without Hardcoding
With RestTemplate and DiscoveryClient (Manual Load Balancing)
java
@Service
public class OrderService {
private final RestTemplate restTemplate;
private final DiscoveryClient discoveryClient;
public OrderService(RestTemplate restTemplate, DiscoveryClient discoveryClient) {
this.restTemplate = restTemplate;
this.discoveryClient = discoveryClient;
}
public String getProduct(Long id) {
// Fetch all registered instances of "product-service"
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
if (instances.isEmpty()) {
throw new ServiceUnavailableException("No product-service instances available");
}
// Manual load balancing — always picks first instance (replace with real algorithm)
ServiceInstance instance = instances.get(0);
String url = instance.getUri() + "/product/" + id;
return restTemplate.getForObject(url, String.class);
}
}This works but requires writing your own load balancing algorithm. The next chapter introduces Spring Cloud LoadBalancer, which handles this automatically.
With FeignClient and Service Discovery (Zero Manual Work)
When service discovery is active, FeignClient resolves the name automatically. Instead of providing a URL, provide only the application name:
java
@FeignClient(name = "product-service") // URL is omitted — resolved via Eureka
public interface ProductClient {
@GetMapping("/product/{id}")
String getProductById(@PathVariable("id") Long id);
}The framework fetches instances from Eureka using the name product-service, applies a load balancing algorithm to pick one instance, and invokes the endpoint on that instance. No URL property is needed in application.properties.
How Eureka Knows Whether a Client Is Alive
Graceful Shutdown (Deregistration)
When a client application is shut down cleanly (e.g., SIGTERM from Kubernetes or IDE stop button), the Eureka client sends a deregistration request to the server. The server marks the instance as DOWN and eventually removes it from the registry.
Log output during graceful shutdown:
Unregistering application PRODUCT-SERVICE with eureka with status DOWNHeartbeats (Lease Renewal)
If the client is killed abruptly (crash, OOM, network partition), no deregistration request is sent. Eureka detects this via heartbeats:
Every client periodically sends a renewal request to the Eureka server. If the server does not receive a heartbeat within the configured lease expiration window, it removes the instance.
properties
# Client configuration (product-service application.properties)
# How often (seconds) the client sends a heartbeat to the server
eureka.instance.lease-renewal-interval-in-seconds=30
# How long (seconds) the server waits for a heartbeat before evicting the instance
eureka.instance.lease-expiration-duration-in-seconds=90properties
# Server configuration (service-discovery application.properties)
# Allow server to remove instances that stop sending heartbeats
eureka.server.enable-self preservation=false
# How often (seconds) the server runs its eviction check
eureka.server.eviction-interval-timer-in-ms=6000Self-preservation mode (enable-self preservation=true is the default): if the server receives fewer heartbeats than expected (perhaps due to network issues), it assumes the network is partitioned rather than that all the clients died, and it stops evicting instances. This is safe in production to avoid mass eviction during transient network problems. In local development, set it to false so the server aggressively cleans up dead instances.
Where and How Eureka Stores Data
java
// Conceptual structure inside Eureka Server
Map<String, Map<String, Lease<InstanceInfo>>> registry;
// ^app-name ^instance-id ^lease with metadataKey fields in InstanceInfo:
appName— e.g.,PRODUCT-SERVICEinstanceId— e.g.,192.168.1.5:product-service:8082ipAddrandportstatus—UP,DOWN,STARTING,OUT_OF_SERVICElastUpdatedTimestampleaseInfo— duration, last renewal timestamp
There is no database persistence. If the Eureka server restarts, it starts with an empty registry. Clients reregister automatically on their next heartbeat cycle.
Eureka Server High Availability — Cluster Setup
A single Eureka server is a single point of failure. Production deployments run a cluster of three servers (odd number for quorum). Each server is simultaneously a client of the other two — it registers with them and fetches their registry — so all three stay eventually consistent.
Server 1 — application.properties
properties
server.port=8761
spring.application.name=eureka-server
eureka.instance.hostname=eureka-server-1
eureka.instance.prefer-ip-address=false
# Server 1 is a client of Server 2 and Server 3
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=\
http://eureka-server-2:8762/eureka,\
http://eureka-server-3:8763/eurekaServer 2 and Server 3 follow the same pattern, listing the other two servers in defaultZone.
Client configuration pointing to all three servers:
properties
eureka.client.service-url.defaultZone=\
http://eureka-server-1:8761/eureka,\
http://eureka-server-2:8762/eureka,\
http://eureka-server-3:8763/eurekaIf one server is unavailable, the client falls back to the next URL in the list. Eureka servers use eventual consistency — data is replicated asynchronously, so there may be a brief window where a newly registered instance appears on one server but not yet on the others.
Local Registry Cache and Latency
Eureka does not introduce per request latency. At application startup, the client fetches the full registry and stores it locally. All subsequent lookups use the local copy.
properties
# How often (seconds) the client refreshes its local registry cache from the server
eureka.client.registry-fetch-interval-seconds=30trade off: Stale Cache
If an instance goes down but the client has not yet refreshed its cache, the client may try to call the dead instance. Configuring a very short refresh interval reduces stale data but increases load on the Eureka server. A very long refresh interval reduces server load but increases the window of stale data. Choose a value that balances these concerns — 30 seconds is a common production default.
Interview Questions & Pitfalls
Q1. What are the two things a Eureka client typically does, and what configuration controls each?
A client registers itself with the server (eureka.client.register-with-eureka=true) and fetches the registry from the server (eureka.client.fetch-registry=true). Both default to true. You might disable registration if you have a client-only service (e.g., a reporting service that only calls others but should not be called). You might disable fetching if a service only receives calls and never makes its own outgoing calls.
Q2. How does Eureka detect that a client instance has gone down?
Via two mechanisms: graceful deregistration (the client sends a status-down request during controlled shutdown) and heartbeat expiry (the server evicts an instance if it does not receive a heartbeat within the lease-expiration-duration-in-seconds window). The server periodically runs an eviction check controlled by eureka.server.eviction-interval-timer-in-ms.
Q3. Is Eureka an AP or CP system in the CAP theorem sense?
Eureka is AP — it prioritizes Availability and Partition Tolerance over strict Consistency. Data is stored in memory with eventual consistency between cluster nodes. During a network partition, each Eureka node continues to serve stale registry data rather than refusing requests. This is intentional: it is better to route traffic to a potentially stale list of instances than to make discovery unavailable.
Q4. Why does Eureka use an in memory store with no database persistence?
Speed and simplicity. Registry data is volatile by design — instances come and go, and stale entries are evicted via heartbeats. Persisting to a database would add latency to every registration and heartbeat operation and introduce a dependency on an external data store. Upon restart, clients reregister within one heartbeat cycle, so the registry converges to the correct state quickly.
Q5. What is self preservation mode and when should you disable it?
Self-preservation mode prevents the server from evicting instances when it detects that more than a configured threshold of clients have missed their heartbeats simultaneously. This protects against mass eviction during transient network issues. It should be disabled (eureka.server.enable-self preservation=false) in local development environments where you want fast cleanup of dead instances. In production, leave it enabled.
Q6. Does using Eureka add latency to every service to service call?
No. Eureka uses a local cache on each client. The registry is fetched once at startup and refreshed periodically (default every 30 seconds). Every actual service call resolves the target instance from the local cache without contacting the Eureka server. The Eureka server is only contacted during registration, heartbeats, and cache refreshes.
Q7. What happens if the Eureka server goes down after all clients have registered?
Clients continue to use their locally cached registry copies. They cannot register new instances or receive updates about instance status changes until the server recovers. If the server is configured as a cluster (three nodes), the client automatically fails over to another server node. This is why single node Eureka is only acceptable for development.