Contents
- Eureka Server Setup
- Client Registration
- Health Checks & Metadata
- Querying the Registry with DiscoveryClient
- Client-Side Load Balancing
- Self-Preservation Mode
- High Availability — Peer-Aware Replication
- Zones & Region-Aware Routing
<!-- Eureka Server pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
# application.yml — standalone Eureka Server (dev)
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # this server doesn't register itself
fetch-registry: false # doesn't need a copy of the registry
service-url:
defaultZone: http://localhost:8761/eureka/
server:
enable-self-preservation: false # disable in dev (easier to test)
eviction-interval-timer-in-ms: 5000
The Eureka dashboard is available at http://localhost:8761 — it shows all registered instances, their status, and metadata.
<!-- Each microservice pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
# application.yml — each microservice
spring:
application:
name: order-service # this is the name other services use to find you
eureka:
client:
service-url:
defaultZone: http://eureka-server:8761/eureka/
registry-fetch-interval-seconds: 10 # how often to refresh local copy
instance-info-replication-interval-seconds: 10
instance:
prefer-ip-address: true # register with IP instead of hostname
ip-address: ${POD_IP:127.0.0.1} # use pod IP in Kubernetes
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
lease-renewal-interval-in-seconds: 10 # heartbeat interval
lease-expiration-duration-in-seconds: 30
// @EnableDiscoveryClient is auto-enabled when spring-cloud-starter-netflix-eureka-client
// is on the classpath. No annotation needed in Spring Boot 3.
// Explicitly enable if you want to be self-documenting:
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication { }
# By default, Eureka registers an instance as UP as soon as it starts.
# Use Actuator health to make registration dependent on app health:
eureka:
client:
healthcheck:
enabled: true # use /actuator/health to determine Eureka UP/DOWN/OUT_OF_SERVICE
management:
endpoints:
web:
exposure:
include: health,info
health:
defaults:
enabled: true
// Add custom metadata to the registration (visible in dashboard + queryable by clients)
@Configuration
public class EurekaMetadataConfig {
@Bean
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils);
config.getMetadataMap().put("version", "2.1.0");
config.getMetadataMap().put("region", "us-east-1");
config.getMetadataMap().put("environment", "prod");
return config;
}
}
// Or via YAML
// eureka.instance.metadata-map.version: 2.1.0
// eureka.instance.metadata-map.region: us-east-1
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.ServiceInstance;
@Service
@RequiredArgsConstructor
public class ServiceLocator {
private final DiscoveryClient discoveryClient;
// List all registered services
public List<String> allServices() {
return discoveryClient.getServices();
}
// Get all instances of a service
public List<ServiceInstance> instances(String serviceName) {
return discoveryClient.getInstances(serviceName);
}
// Get a single instance URL
public URI getServiceUri(String serviceName) {
return discoveryClient.getInstances(serviceName)
.stream()
.findFirst()
.map(ServiceInstance::getUri)
.orElseThrow(() -> new ServiceNotFoundException(serviceName));
}
// Access instance metadata
public String getVersion(String serviceName) {
return discoveryClient.getInstances(serviceName)
.stream()
.findFirst()
.map(i -> i.getMetadata().get("version"))
.orElse("unknown");
}
}
// Spring Cloud LoadBalancer auto-wires with @LoadBalanced RestTemplate or WebClient
@Configuration
public class WebClientConfig {
// Load-balanced WebClient — resolves "order-service" via Eureka, round-robins instances
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
@RequiredArgsConstructor
public class OrderProxy {
private final WebClient.Builder webClientBuilder;
private final RestTemplate restTemplate;
public Mono<Order> getOrder(Long id) {
return webClientBuilder.build()
.get()
.uri("http://order-service/orders/{id}", id) // "order-service" resolved via Eureka
.retrieve()
.bodyToMono(Order.class);
}
public Order getOrderSync(Long id) {
return restTemplate.getForObject(
"http://order-service/orders/{id}", Order.class, id);
}
}
# Load balancer configuration
spring:
cloud:
loadbalancer:
cache:
enabled: true # cache service instances locally
ttl: 35s # refresh every 35 seconds
retry:
enabled: true
max-retries-on-same-service-instance: 0
max-retries-on-next-service-instance: 1
Self-preservation prevents Eureka from evicting instances when it detects a network partition (many heartbeats failing at once). Instead of evicting what might be healthy services it cannot reach, Eureka keeps all registrations until the situation resolves.
# Eureka Server
eureka:
server:
enable-self-preservation: true # default — recommended for production
renewal-percent-threshold: 0.85 # if <85% of expected heartbeats received, enter self-preservation
renewal-threshold-update-interval-ms: 900000 # recalculate threshold every 15 min
# Development — disable self-preservation for faster instance eviction during testing
eureka:
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000 # check every 2 seconds (dev only)
In production keep self-preservation enabled. Without it, a brief network hiccup can cause Eureka to evict healthy services, routing all traffic to a subset of instances and causing cascading failures. Clients should also implement circuit breakers as a second line of defence.
Run multiple Eureka Server instances. Each peer registers with the others and replicates the registry — if one server goes down, clients fall back to a cached copy and the remaining peers.
# application-eureka1.yml
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true # peers DO register with each other
fetch-registry: true
service-url:
defaultZone: http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
---
# application-eureka2.yml
server:
port: 8762
eureka:
instance:
hostname: eureka2
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka3:8763/eureka/
# Clients point to all Eureka servers (comma-separated) — tries first, falls back to others
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
# Instance declares its zone
eureka:
instance:
metadata-map:
zone: us-east-1a
# Client prefers instances in the same zone
eureka:
client:
prefer-same-zone-eureka: true
availability-zones:
us-east-1: us-east-1a, us-east-1b
region: us-east-1
service-url:
us-east-1a: http://eureka-east-1a:8761/eureka/
us-east-1b: http://eureka-east-1b:8762/eureka/
For Kubernetes deployments, consider replacing Eureka with Kubernetes-native service discovery (spring-cloud-kubernetes) which uses the Kubernetes API to list pod endpoints. This eliminates the need to run a separate Eureka cluster.