Service Discovery, Config & Gateway

Service Registry with Eureka

18 min Lesson 2 of 12

Service Registry with Eureka

In the previous lesson you saw why hard-coding service addresses breaks down as soon as you run more than one instance of anything. A service registry is the solution: a central store where every service instance announces itself on startup and deregisters on shutdown. Any caller can then ask the registry for a live address instead of having it baked into a config file.

Spring Cloud ships first-class support for Netflix Eureka, which has been battle-tested in large-scale production systems. Eureka follows a simple model: a dedicated Eureka Server holds the registry, and each microservice runs a Eureka Client that registers, renews a heartbeat, and fetches the registry for use by client-side load balancing.

Standing Up the Eureka Server

Start a new Spring Boot 3 project and add two dependencies:

<!-- pom.xml --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

Then annotate the main class with @EnableEurekaServer:

package com.example.registry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class RegistryApplication { public static void main(String[] args) { SpringApplication.run(RegistryApplication.class, args); } }

The server needs a minimal application.yml that tells it not to register with itself (it is the registry, not a client):

server: port: 8761 spring: application: name: eureka-server eureka: client: register-with-eureka: false fetch-registry: false server: wait-time-in-ms-when-sync-empty: 0 # skip wait on first boot

Run the server and open http://localhost:8761. You will see the Eureka dashboard — currently empty, ready for registrations.

Why port 8761? It is the Eureka default that Spring Cloud clients use out of the box. Keeping it as-is means client services need no explicit eureka.client.service-url in development, which removes a whole class of misconfiguration.

Registering a Service: The Eureka Client

Every 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>

No annotation is needed in Spring Boot 3 / Spring Cloud 2023 — presence of the starter on the classpath activates auto-configuration. All you need is the service name in application.yml:

server: port: 8081 spring: application: name: order-service # this becomes the virtual hostname in the registry eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true # clients resolve IP, not hostname lease-renewal-interval-in-seconds: 10 # heartbeat every 10 s lease-expiration-duration-in-seconds: 30 # server evicts after 30 s of silence

Start this service and refresh the Eureka dashboard. You will see ORDER-SERVICE appear in the "Instances currently registered with Eureka" table with its IP, port, and status UP.

What Happens at Registration Time

When the Eureka client starts it performs an HTTP POST to /eureka/apps/{appName} on the server, sending a JSON payload called an InstanceInfo. This contains:

  • Application name (order-service, uppercased)
  • Host and port
  • Health-check URL (defaults to the Spring Boot Actuator /actuator/health endpoint if present)
  • Status — initially STARTING, then UP once the context is ready
  • Metadata map — arbitrary key-value pairs you can use for routing hints

After registration, the client sends a PUT heartbeat (renewal) every lease-renewal-interval-in-seconds seconds. If the server receives no renewal within lease-expiration-duration-in-seconds seconds it marks the instance DOWN and eventually evicts it.

Keep heartbeat and expiry ratios consistent. The industry default (30 s interval / 90 s expiry) gives three missed heartbeats before eviction. Tightening the interval to 10 s / 30 s makes the registry react faster in development, but in production a momentary GC pause could cause a false eviction. Tune conservatively in production.

Discovering Services Programmatically

Once a service is registered, any other Eureka client in the same cluster can resolve it by name. The simplest way is DiscoveryClient, injected by Spring:

import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Component; import java.util.List; @Component public class OrderServiceClient { private final DiscoveryClient discoveryClient; public OrderServiceClient(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } public String getOrderServiceBaseUrl() { List<ServiceInstance> instances = discoveryClient.getInstances("order-service"); if (instances.isEmpty()) { throw new IllegalStateException("No instances of order-service are registered"); } // pick the first one; lesson 3 covers proper load balancing ServiceInstance instance = instances.get(0); return instance.getUri().toString(); // e.g. http://192.168.1.42:8081 } }

The getInstances() call returns every live instance registered under that name. The URI is already fully assembled from the instance metadata. In practice you rarely call DiscoveryClient directly — you let Spring Cloud LoadBalancer (covered in the next lesson) pick an instance for you — but understanding the raw API is essential for debugging and for custom routing logic.

Security Considerations for the Registry

The Eureka dashboard and REST API are unauthenticated by default. In any environment where the registry is reachable from untrusted networks this is a critical misconfiguration: a malicious client could register a fake instance and redirect traffic to it (a service-spoofing attack).

The standard mitigation is to add Spring Security to the Eureka Server project and require HTTP Basic authentication, then embed the credentials in the client URLs:

# Eureka Server — application.yml spring: security: user: name: eureka password: ${EUREKA_PASSWORD} # read from environment, never hardcoded # Eureka Client — application.yml eureka: client: service-url: defaultZone: http://eureka:${EUREKA_PASSWORD}@localhost:8761/eureka/
Never expose the Eureka dashboard on a public interface without authentication. A full registry listing tells an attacker every internal service name, IP, port, and health endpoint in your system — everything needed to map your entire microservices topology. In production, keep Eureka on an internal network and add TLS + credentials at minimum.

Self-Preservation Mode

Eureka has a built-in mechanism called self-preservation mode. If the server stops receiving renewals from a significant fraction of registered instances (above a configurable threshold), it assumes the network is experiencing a partition rather than that all those services have gone down, and it stops evicting instances. This preserves stale but potentially still-live entries rather than emptying the registry during a network blip.

Self-preservation is appropriate for production but can be confusing in development, where stopping a service should immediately remove it. You can disable it per environment:

# Eureka Server — application.yml (dev profile only) eureka: server: enable-self-preservation: false eviction-interval-timer-in-ms: 2000 # check for expired leases every 2 s
CAP theorem in practice: Eureka prioritises Availability over Consistency. During a network partition it keeps returning (possibly stale) registry data so that clients can keep calling services rather than failing fast. This is deliberately different from CP registries like Consul, which would rather return an error than stale data. Know which model your system needs before choosing a registry.

Summary

A Eureka Server is a single Spring Boot application annotated with @EnableEurekaServer. Each client service adds the Eureka client starter and sets spring.application.name; registration and heartbeating are automatic. Other services discover live instances by name via DiscoveryClient or, more practically, through Spring Cloud LoadBalancer. Always secure the registry endpoint with credentials and TLS in any non-local environment, and understand self-preservation mode so it does not surprise you during incidents.