Service Discovery in Spring Boot Microservices Using Netflix Eureka

Introduction

In a microservices architecture, multiple independent services communicate with each other to perform business operations. For example, an e-commerce application might contain an Order Service, Product Service, Payment Service, and Inventory Service.

But how does one microservice know the address of another microservice?

One simple approach is to hardcode the service URL. However, this approach becomes difficult to manage when services run on multiple instances, their IP addresses change, or instances are added and removed dynamically.

Service Discovery solves this problem by allowing microservices to locate other services dynamically without hardcoding their individual URLs.

In this article, we will learn how Service Discovery works, why it is required, and how to implement it using Spring Boot and Netflix Eureka.

1. What Is Service Discovery?

Service Discovery is a mechanism that helps a microservice find the network location of another microservice at runtime.

In a typical microservices application, service instances may be created, stopped, restarted, or scaled according to application requirements. Their IP addresses and ports can change over time.

Instead of maintaining the URL of every service manually, applications can use a Service Discovery mechanism to find available service instances.

For example, consider an application with the following services:

  • Order Service
  • Product Service
  • Payment Service

When the Order Service needs product details, it discovers the Product Service using its logical name rather than depending on a specific IP address.

Netflix Eureka is one implementation of Service Discovery commonly used with Spring Cloud applications.

2. Why Do We Need Service Discovery?

Let’s understand the problems associated with hardcoded service URLs.

2.1 Single Point of Failure

Suppose the Order Service calls the Product Service using a hardcoded URL:

http://192.168.1.10:8082/products/101

Now imagine that this Product Service instance goes down while other instances are still running.

The Order Service continues trying to access the unavailable instance because it does not automatically know about the other instances.

Service Discovery allows the Order Service to obtain information about registered Product Service instances.

2.2 No Load Balancing

Suppose three Product Service instances are running, but the Order Service always calls the same instance.

The selected instance may become overloaded while the remaining instances stay idle.

Service Discovery provides the information needed to locate available instances. A load balancer can then distribute requests among them.

2.3 Tight Coupling

When a microservice depends on a hardcoded URL, changing the target service’s network location may require updating the calling service’s configuration.

This creates additional maintenance work and makes independent deployment more difficult.

Using logical service names reduces this dependency on individual instance addresses.

2.4 Difficulty Managing Multiple Environments

Development, testing, QA, and production environments often use different service addresses.

Managing these URLs manually increases configuration complexity and the risk of errors.

Service Discovery helps applications locate services through a registry instead of relying on manually maintained instance URLs.

3. How Does Service Discovery Work?

Service Discovery generally involves two important components: Eureka Server and Eureka Client.

Eureka Server

Eureka Server acts like a phonebook for microservices.

It maintains information about registered service instances, such as:

  • Service name
  • Instance ID
  • IP address or hostname
  • Port number
  • Instance status

Eureka Client

Eureka Client is a microservice that communicates with the Eureka Server.

Its responsibilities include registering the service instance, renewing its registration through heartbeats, and retrieving information about other registered services.

Consider the following architecture:

       +-------------------+
       |   Eureka Server   |
       |  Service Registry|
       +-------------------+
          ^             ^
          |             |
      Register       Register
          |             |
   +--------------+ +----------------+
   | Order Service| | Product Service|
   +--------------+ +----------------+
          |
          | Discover product-service
          v
   +-------------------------+
   | Product Service Instance|
   +-------------------------+

The Order Service discovers the Product Service through its logical name, product-service.

The registry provides information about available instances, while a suitable client-side load balancer can select an instance for the request.

4. Implementing Service Discovery Using Spring Boot

Let’s implement Service Discovery using Spring Boot and Netflix Eureka.

We will create three applications:

  1. Eureka Server
  2. Product Service
  3. Order Service

The Eureka Server maintains the registry, while the Product and Order services register themselves as Eureka Clients.

Step 1: Create the Eureka Server

Create a Spring Boot project using Spring Initializr.

Add the following dependency to the Eureka Server’s pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

Configure Spring Cloud dependency management with a release compatible with your Spring Boot version.

For example, the reference tutorial uses Spring Cloud 2023.0.1.

Step 2: Enable Eureka Server

Create the main application class:

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                EurekaServerApplication.class, args);
    }
}

The @EnableEurekaServer annotation enables Eureka Server functionality in the application.

Step 3: Configure Eureka Server Properties

Add the following configuration to application.properties:

spring.application.name=eureka-server
server.port=8761

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Explanation:

  • spring.application.name defines the application name.
  • server.port=8761 configures the server port.
  • register-with-eureka=false prevents the standalone server from registering itself.
  • fetch-registry=false prevents it from fetching registry information from another Eureka Server.

Start the application and open:

http://localhost:8761

The Eureka dashboard should now be available.

5. Register Product Service with Eureka

The Product Service must include the Eureka Client dependency.

Add the following dependency to its pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Use the same compatible Spring Cloud dependency management as the Eureka Server.

Configure Product Service

Add the following properties:

server.port=8082
spring.application.name=product-service

eureka.client.service-url.defaultZone=http://localhost:8761/eureka
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true

Here, spring.application.name=product-service identifies the service in the registry.

The defaultZone property specifies the Eureka Server address.

Start the Product Service while Eureka Server is running.

Refresh the dashboard at http://localhost:8761.

You should see PRODUCT-SERVICE listed among the registered applications if registration succeeds.

The Order Service can be registered using the same approach.

6. Calling Product Service Without Service Discovery

Suppose the Order Service needs to retrieve product information.

Using RestTemplate, the code might look like this:

public String getProduct(String id) {

    RestTemplate restTemplate = new RestTemplate();

    return restTemplate.getForObject(
            "http://localhost:8082/products/" + id,
            String.class
    );
}

This code explicitly specifies the Product Service URL.

If the service moves to another host or port, the URL must be updated. The code also does not automatically select among multiple Product Service instances.

Let’s see how Service Discovery can help.

7. Using DiscoveryClient

Spring Cloud provides the DiscoveryClient interface to retrieve service instances by their logical names.

Example:

import java.net.URI;
import java.util.List;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.client.RestTemplate;

@Autowired
private DiscoveryClient discoveryClient;

public String getProduct(String id) {

    List<ServiceInstance> instances =
            discoveryClient.getInstances("product-service");

    if (instances.isEmpty()) {
        throw new IllegalStateException(
                "No Product Service instances available");
    }

    URI serviceUri = instances.get(0).getUri();

    RestTemplate restTemplate = new RestTemplate();

    return restTemplate.getForObject(
            serviceUri + "/products/" + id,
            String.class
    );
}

How does this code work?

  1. getInstances("product-service") retrieves the registered Product Service instances.
  2. getUri() obtains the network URI of the selected instance.
  3. RestTemplate sends the HTTP request to that instance.

Notice that this example selects the first instance returned. It demonstrates discovery, but it does not implement a load-balancing strategy. In production, instance selection should account for load balancing and service failures.

8. Using FeignClient with Service Discovery

Spring Cloud OpenFeign provides a declarative way to communicate with other microservices.

Instead of manually writing HTTP request code, we define a Java interface.

FeignClient Without Service Discovery

A client can use a configured URL:

@FeignClient(
    name = "product-service",
    url = "${feign.client.product-service.url}"
)
public interface ProductClient {

    @GetMapping("/products/{id}")
    String getProductById(@PathVariable("id") String id);
}

The URL property specifies the target address.

FeignClient With Service Discovery

When Eureka-based service discovery is configured, the URL can be omitted:

@FeignClient(name = "product-service")
public interface ProductClient {

    @GetMapping("/products/{id}")
    String getProductById(@PathVariable("id") String id);
}

The name product-service is resolved through the configured discovery and load-balancing infrastructure.

Add the Spring Cloud LoadBalancer dependency:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

Also include the Spring Cloud OpenFeign dependency and enable Feign clients in the calling application.

For example:

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.SpringApplication;

@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                OrderServiceApplication.class, args);
    }
}

You can now inject ProductClient into a service class and call its methods.

FeignClient reduces boilerplate and integrates with Spring Cloud LoadBalancer when correctly configured.

9. How Does Eureka Know Whether a Service Is UP or DOWN?

Service instances may stop gracefully, crash unexpectedly, or become unreachable due to network problems.

Eureka uses registration, de-registration, and heartbeat mechanisms to maintain registry information.

Client De-registration

When a Eureka Client shuts down gracefully, it can send a de-registration request to Eureka Server.

The server can then remove the instance from its registry.

Heartbeat Mechanism

Eureka Clients periodically send heartbeats to renew their registration leases.

For example:

eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90

The first property specifies the heartbeat renewal interval, while the second specifies the lease-expiration duration.

If heartbeats stop for long enough, Eureka can evict the expired instance, depending on its server configuration and self-preservation behavior.

A heartbeat does not guarantee that every business endpoint is healthy. It indicates that the instance is renewing its registration lease.

Avoid setting the lease-expiration duration shorter than the heartbeat interval during normal operation. Otherwise, a healthy instance could be removed before its next heartbeat arrives.

10. Where Does Eureka Store Service Information?

In the standalone configuration described here, Eureka maintains registry information in memory.

Conceptually, the registry can be represented as:

Map<String, Lease<InstanceInfo>>

The registry contains information such as:

  • Application name
  • Instance ID
  • IP address or hostname
  • Port number
  • Instance status
  • Last renewal timestamp
  • Lease information

Because the registry is maintained in memory, a standalone Eureka Server should not be treated as a durable database for service registrations.

After a restart, clients may need to register again.

11. What Happens If Eureka Server Goes Down?

A standalone Eureka Server can become a single point of failure for registry operations.

Clients that already have a local registry cache may continue discovering instances from that cache, but stale information or network failures can still affect service calls.

For better availability, Eureka supports peer-aware deployments in which multiple Eureka Servers communicate with each other and replicate registry information.

For example, a deployment may use three Eureka Servers on ports 8761, 8762, and 8763.

In this arrangement, each server is configured with the addresses of the other peers. Client applications can also be configured with multiple Eureka Server endpoints.

A correctly configured cluster reduces dependence on a single server. Production deployments should use appropriate hostnames, network settings, and failure-handling configurations.

12. Does Service Discovery Increase API Latency?

Service Discovery does not necessarily add an extra Eureka network call to every API request.

Eureka Clients normally retrieve the registry and maintain a local cache.

The general process is:

  1. The client starts and retrieves registry information.
  2. The client caches the information locally.
  3. The client resolves service names using the cached registry.
  4. The client periodically refreshes its registry.

The reference tutorial illustrates the following setting:

eureka.client.registry-fetch-interval-seconds=30

This configures a registry refresh interval of 30 seconds in the described setup.

Because clients use cached registry information, Eureka does not normally need to be contacted for every business API request.

Actual request latency still depends on network conditions, the selected instance, load balancing, and the target service’s response time.

13. What Happens When the Local Cache Is Stale?

Suppose the Order Service has cached the following Product Service instance:

http://192.168.1.10:8082

If that instance crashes before the registry information is refreshed, the Order Service might still attempt to call it.

This is a normal challenge in distributed systems.

Service Discovery helps locate instances, but it cannot guarantee that every registered or cached instance is immediately reachable.

To handle these situations:

  • Configure appropriate connection and response timeouts.
  • Use a suitable load-balancing strategy.
  • Configure registry refresh and lease settings carefully.
  • Apply retry policies for transient failures.
  • Consider circuit breakers for repeated failures.

Retries should be designed carefully, particularly for operations such as order creation or payment processing, where repeating a request can cause duplicate operations.

14. Service Discovery vs. Load Balancing

Service Discovery and Load Balancing are related, but they serve different purposes.

Service Discovery Load Balancing
Finds registered service instances. Selects an instance for a request.
Maintains or retrieves service location information. Distributes requests among instances.
Example: Netflix Eureka. Example: Spring Cloud LoadBalancer.

In a Spring Cloud application, Eureka can provide information about available instances, while Spring Cloud LoadBalancer can select an instance for a request.

Together, they help microservices communicate without relying on fixed instance URLs.

15. Frequently Asked Interview Questions

1. What is Service Discovery?

Service Discovery allows microservices to locate other service instances dynamically without hardcoding their network addresses.

2. What is Netflix Eureka?

Netflix Eureka is a service registry used for registering and discovering service instances in distributed applications.

3. What is the difference between Eureka Server and Eureka Client?

Eureka Server maintains the registry. Eureka Clients register their instances and retrieve information about other services.

4. How does Eureka detect an unavailable service?

Eureka Clients can de-register during graceful shutdown and periodically renew their registration leases through heartbeats. Missing renewals can lead to lease expiration and eviction.

5. Does Eureka perform load balancing?

Eureka provides service-instance information. A load balancer such as Spring Cloud LoadBalancer can select an instance for a request.

6. Does Eureka add latency to every API request?

Not necessarily. Clients normally use locally cached registry information instead of querying Eureka for every request.

7. Is Eureka Server a single point of failure?

A standalone Eureka Server can be a single point of failure. A correctly configured Eureka cluster can improve availability.

8. What happens when a client has stale registry information?

The client may try to access an unavailable instance. Timeouts, retries, load balancing, and circuit breakers can help manage such failures.

Conclusion

Service Discovery is an important concept in Spring Boot microservices because service instances can change dynamically.

Netflix Eureka provides a registry through which services can register themselves and discover other services using logical names. Spring Cloud LoadBalancer can work with Eureka to select service instances, while FeignClient simplifies communication between microservices.

By combining these components, developers can reduce hardcoded URLs, simplify service communication, and build more flexible microservices architectures.

Leave a Reply