Back to Microservice Course

Microservice Course

Netflix Eureka — Service Registry and Discovery

A microservice instance needs to register itself with a service registry so other services can locate it without hard-coding hostnames or ports. This is called service discovery, and it's essential in dynamic cloud environments where instances start, stop, and move frequently.

When a microservice instance registers with a discovery agent like Eureka, it provides:

  • Its physical IP address or hostname and port
  • A logical service name (the name other clients will use to look it up)
  • A health-check URL so Eureka can monitor its liveness

The service client then queries the discovery agent to get the instance addresses for the service it wants to call, and routes traffic to one of them.

screenshot of the app

In a cloud-based microservice application, multiple instances of a service typically run simultaneously for redundancy and scale. Eureka continuously monitors registered instances via heartbeats. If an instance stops sending heartbeats within a configurable timeout, Eureka removes it from its routing table so clients aren't directed to a dead instance.

screenshot of the app

Spring Cloud Service Discovery, using Eureka or Consul as the backing registry, allows you to:

  • Abstract away physical IP addresses from client code — clients use logical service names
  • Handle instance registration and deregistration automatically as services start and stop
  • Support multiple registered instances of the same service for load balancing

Important note: @EnableDiscoveryClient became unnecessary in most Spring Cloud versions — the presence of a discovery client library on the classpath (e.g., spring-cloud-starter-netflix-eureka-client) and the configuration properties are sufficient. Many projects still include it for explicitness, but it's not required from Spring Cloud 2021 onwards.

Setting Up the Eureka Server

Add the Eureka server dependency:

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

Annotate the main class:

@SpringBootApplication @EnableEurekaServer public class SpringCloudEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaServerApplication.class, args); } }

Configure application.yml:

eureka: client: register-with-eureka: false fetch-registry: false server: eviction-interval-timer-in-ms: 1000 spring: application: name: praveenoruganti-eureka-server server: port: 8761

register-with-eureka: false and fetch-registry: false tell this server not to register with itself as a client, which would create a self-registration loop in a single-node setup.

Run the server: java -jar target/eureka-server-1.0.jar

Open http://localhost:8761 to access the Eureka dashboard.

screenshot of the app screenshot of the app

Setting Up a Eureka Client — Billing Service

Add the client dependency:

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

Main class:

package com.praveen.billing; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient // Optional in modern Spring Cloud — auto-configured from classpath public class BillingServiceApplication { public static void main(String[] args) { SpringApplication.run(BillingServiceApplication.class); } }

application.yml for the billing service:

management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS eureka: client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: hostname: localhost server: port: 8091 spring: application: name: praveenoruganti-billing-service

Start two instances on different ports:

java -Dserver.port=8091 -jar target/billing-service-1.0.jar java -Dserver.port=8092 -jar target/billing-service-1.0.jar

Both instances register with Eureka under the same logical name (praveenoruganti-billing-service).

Setting Up the Order Management Service Client

Add the Eureka client dependency (same as above).

Main class — note: RestTemplate must be annotated with @LoadBalanced to enable Eureka-aware load balancing:

package com.praveen.ordermanagement; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class OrderManagementServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderManagementServiceApplication.class); } @LoadBalanced @Bean RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } }

application.yml:

eureka: client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: hostname: localhost server: port: 8090 spring: application: name: praveenoruganti-ordermanagement-service praveenoruganti-ordermanagement-service: billingURL: http://praveenoruganti-billing-service/api/v1_0/billing/{id}

Service call using RestTemplate:

public String createOrder(String orderId) throws Exception { Map<String, String> uriVariables = new HashMap<>(); uriVariables.put("id", orderId); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<>(null, headers); String message = restTemplate.exchange(billingURL, HttpMethod.POST, entity, String.class, uriVariables).getBody(); log.info("Response received from Billing Service: {}", message); return message; }

Because the RestTemplate is @LoadBalanced and Eureka is configured, the hostname in the URL (praveenoruganti-billing-service) is treated as a logical service name, not a DNS hostname. Spring Cloud LoadBalancer (formerly Ribbon) resolves it to an actual instance address at call time, choosing between the two registered instances (8091, 8092).

Run the order management service: java -jar target/ordermanagement-service-1.0.jar

Visit http://localhost:8761 and you'll see all three services registered.

screenshot of the app

Test with a POST to http://localhost:8090/api/v1_0/order:

{ "orderId": "501", "orderDescription": "Mobile", "correlationId": "258527852785275275" }

Expected response: Successfully placed the order 501

When you send multiple requests, load balancing distributes them across the two billing service instances on ports 8091 and 8092.

Modern alternatives: In Kubernetes-native deployments, Eureka is often replaced by Kubernetes Service objects and kube-dns. WebClient (reactive, non-blocking) is increasingly preferred over RestTemplate for new services, and Spring Cloud OpenFeign provides a declarative REST client that integrates with Eureka and LoadBalancer with less boilerplate than raw RestTemplate calls.