Spring Boot
Eureka Server
How to run a Eureka service registry with Spring Cloud: dependencies and version matching, server setup, securing it, registering client services and calling them by name, the heartbeat and self-preservation timings that explain why dead instances linger, a two-node high-availability setup, a Docker Compose example and a test. It also lists the errors people hit most, and when a platform-native alternative is the better choice.
Dependencies and Version Matching
A Eureka Server is an ordinary Spring Boot web application plus the Eureka server starter. The one rule that causes most startup failures is version matching: each Spring Cloud release train works with specific Spring Boot versions, and a mismatch fails at startup with a "Spring Boot is not compatible with this Spring Cloud release train" error. Do not copy a version number from an old tutorial; pick the train that matches your Boot version from the table below and import it through the BOM, so every Spring Cloud artifact stays aligned. Eureka is part of Spring Cloud Netflix, which is still maintained for Eureka. If you add Spring Security (recommended, see below), Spring Boot's default settings protect the dashboard and the registry API.
Text
Spring Cloud train | Spring Boot
-----------------------|------------------
2025.1.x (Oakwood) | 4.0.x (4.1 needs 2025.1.2 or later)
2025.0.x (Northfields) | 3.5.x
2024.0.x (Moorgate) | 3.4.x
2023.0.x (Leyton) | 3.2.x / 3.3.x
Always confirm against the official compatibility table for the exact versions you use.
<!-- pom.xml (example for Spring Boot 3.5) -->
<properties>
<spring-cloud.version>2025.0.2</spring-cloud.version>
</properties>
<dependencies>
<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-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>The Server Application
The application class needs one extra annotation, @EnableEurekaServer. Starting it gives you two things: a REST API under /eureka that clients use to register, send heartbeats and read the registry, and a dashboard at the root URL that lists every registered instance. By default the server also behaves like a client of itself, trying to register with and fetch from another registry. For a single standalone server you switch that off in the configuration (next section), otherwise the logs fill with connection errors.
Java
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}Standalone Server Configuration
A single server must not register with itself or download its own registry, so both client behaviours are disabled. Port 8761 is only a convention that clients and tooling expect. Credentials come from environment variables so they never reach version control; Spring Boot turns spring.security.user.name and password into the login for the registry. Setting enable-self-preservation to false is shown here only for local development, where you want crashed instances to vanish quickly. Leave it at its default (true) in production, for the reason explained in the timing section below.
yaml
server:
port: 8761
spring:
application:
name: eureka-server
security:
user:
name: ${EUREKA_USERNAME}
password: ${EUREKA_PASSWORD}
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false # a standalone server does not register with itself
fetch-registry: false # and does not need a copy of its own registry
service-url:
defaultZone: http://localhost:${server.port}/eureka/
server:
enable-self-preservation: false # local development onlySecuring the Registry
An open registry lets anyone register a fake instance and receive traffic meant for a real one, so protect it with HTTP Basic authentication. With spring-boot-starter-security on the classpath the registry is already protected by default, but the default configuration turns on CSRF protection, and Eureka clients register with POST and DELETE requests that carry no CSRF token; they fail with 403 Forbidden. The fix is to exempt only the /eureka/** paths from CSRF, as below. Be aware that Basic authentication sends the password on every request, so use HTTPS between services in production. The username and password are set through spring.security.user in the previous section; for several distinct client accounts, define your own UserDetailsService with a BCrypt password encoder instead of the in-memory default. Avoid User.withDefaultPasswordEncoder(), which is marked unsafe for production.
Java
@Configuration
@EnableWebSecurity
public class EurekaSecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.ignoringRequestMatchers("/eureka/**")) // clients use Basic auth, not browser sessions
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.build();
}
}Registering a Client Service
A service joins the registry by adding spring-cloud-starter-netflix-eureka-client (and spring-boot-starter-actuator for health information) and pointing defaultZone at the server. Registration is automatic once the client starter is present; the old @EnableDiscoveryClient annotation is no longer required. The service's spring.application.name becomes its identity in the registry, so every instance of the same service must use the same name. Three settings are worth setting deliberately. prefer-ip-address registers the IP instead of the hostname, which avoids unresolvable container hostnames in Docker. healthcheck.enabled makes the client report its real actuator health; without it Eureka only knows that the heartbeat thread is alive, so a service with a broken database connection still shows as UP. Credentials embedded in the URL must be percent-encoded if they contain characters such as @ or /.
yaml
spring:
application:
name: order-service
server:
port: 8082
eureka:
client:
service-url:
defaultZone: http://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761/eureka/
healthcheck:
enabled: true # report real health, not just "heartbeat is running"
instance:
prefer-ip-address: true
management:
endpoints:
web:
exposure:
include: healthCalling Another Service by Name
The point of the registry is that callers use the service name instead of a host and port. Mark a RestClient.Builder with @LoadBalanced and Spring Cloud LoadBalancer replaces the name in the URL with a real instance from the registry, spreading calls across all instances that are registered. The client keeps a local copy of the registry and refreshes it periodically, so calls keep working for a while even if the registry itself is briefly unreachable. Combine this with timeouts and a circuit breaker; discovery tells you where a service is, not whether it is healthy right now.
Java
@Configuration
class RestClientConfig {
@Bean
@LoadBalanced
RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
}
@Component
class UserClient {
private final RestClient restClient;
UserClient(@LoadBalanced RestClient.Builder builder) {
this.restClient = builder.baseUrl("http://user-service").build(); // a service name, not a host
}
UserResponse findUser(Long id) {
return restClient.get()
.uri("/api/users/{id}", id)
.retrieve()
.body(UserResponse.class);
}
}Inspecting the Registry
Besides the dashboard, the REST API is the quickest way to see what is registered. It answers in XML unless you send an Accept header for JSON. The same API is what clients use: a POST to /eureka/apps/{name} registers an instance, a PUT to /eureka/apps/{name}/{instanceId} is the heartbeat that renews its lease, and a DELETE removes it. You rarely call those by hand, but knowing them explains what you see in the server logs.
Shell
# All registered applications, as JSON
curl -u "$EUREKA_USERNAME:$EUREKA_PASSWORD" \
-H "Accept: application/json" \
http://localhost:8761/eureka/apps
# Instances of one service
curl -u "$EUREKA_USERNAME:$EUREKA_PASSWORD" \
-H "Accept: application/json" \
http://localhost:8761/eureka/apps/ORDER-SERVICEHeartbeats, Timing and Self-Preservation
Eureka is deliberately slow to react, and understanding why saves hours of confusion. A client sends a heartbeat every 30 seconds. The server evicts an instance only after 90 seconds without one, and the eviction job itself runs every 60 seconds. Responses are then cached on the server for 30 seconds, and clients refresh their own copy every 30 seconds. In the worst case a crashed instance can therefore still be returned to callers for roughly three to four minutes. Self-preservation explains the other surprise. If the share of expected heartbeats arriving drops below a threshold (85% by default), Eureka assumes a network problem rather than mass failure and stops evicting anything, so the registry keeps possibly dead instances rather than wiping out healthy ones. The dashboard then shows a red warning that the registry may be incorrectly claiming instances are up. In production keep the defaults and make callers resilient with timeouts, retries and circuit breakers. For local development you can shorten everything; never use these values in production, where they cause flapping and extra load.
yaml
# Development profile only
eureka:
server: # on the server
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
response-cache-update-interval-ms: 3000
instance: # on each client
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 15
client: # on each client
registry-fetch-interval-seconds: 5High Availability with Peer Replication
One Eureka Server is a single point of failure for new registrations and lookups, so production runs at least two that replicate to each other. Each peer registers with the others and fetches their registry, which is why register-with-eureka and fetch-registry are true here, the opposite of the standalone setup. Three details decide whether the cluster actually works. All peers must share the same spring.application.name. Each peer needs its own eureka.instance.hostname, and that name must match the host used in the other peers' defaultZone; a mismatch makes the dashboard list the peer under "unavailable-replicas". And because security is on, the peers need the credentials in their defaultZone URLs as well. Eureka favours availability over consistency, so peers can briefly show slightly different registries after a change; clients tolerate that because they cache the registry. Clients list all peers in one comma-separated line and fail over automatically.
yaml
# Server application.yml: one file, one profile per peer
spring:
application:
name: eureka-server # identical on every peer
---
spring:
config:
activate:
on-profile: peer1
server:
port: 8761
eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@eureka2:8762/eureka/
---
spring:
config:
activate:
on-profile: peer2
server:
port: 8762
eureka:
instance:
hostname: eureka2
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@eureka1:8761/eureka/
# Client application.yml: list every peer on ONE line, separated by commas
# eureka.client.service-url.defaultZone:
# http://user:pass@eureka1:8761/eureka/,http://user:pass@eureka2:8762/eureka/Running Two Peers with Docker Compose
Compose is the quickest way to try the cluster on one machine. Each service name becomes a hostname on the Compose network, so eureka1 and eureka2 resolve to each other and match the hostnames in the profiles above. The image is whatever you built from the server project. After startup, open the first peer's dashboard: the second one should appear under DS Replicas, and a client registered with either server should show up in both.
yaml
services:
eureka1:
image: eureka-server:latest
environment:
SPRING_PROFILES_ACTIVE: peer1
EUREKA_USERNAME: ${EUREKA_USERNAME}
EUREKA_PASSWORD: ${EUREKA_PASSWORD}
ports:
- "8761:8761"
eureka2:
image: eureka-server:latest
environment:
SPRING_PROFILES_ACTIVE: peer2
EUREKA_USERNAME: ${EUREKA_USERNAME}
EUREKA_PASSWORD: ${EUREKA_PASSWORD}
ports:
- "8762:8762"Testing the Server
A short integration test is worth having because a security or configuration mistake otherwise only shows up when the first client fails to register. The test starts the server on a random port with registration disabled and checks the two properties that matter: anonymous callers are rejected, and callers with credentials can read the registry. On Spring Boot 4, TestRestTemplate lives in a separate test module, so check the migration notes for the dependency and package.
Java
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"eureka.client.register-with-eureka=false",
"eureka.client.fetch-registry=false",
"spring.security.user.name=test",
"spring.security.user.password=test"
})
class EurekaServerApplicationTest {
@Autowired
private TestRestTemplate rest;
@Test
void registryRejectsAnonymousCallers() {
ResponseEntity<String> response = rest.getForEntity("/eureka/apps", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
void registryIsReadableWithCredentials() {
ResponseEntity<String> response = rest.withBasicAuth("test", "test")
.getForEntity("/eureka/apps", String.class);
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
}
}Errors You Will Probably Hit
Most Eureka problems are one of a handful of causes. The table maps the symptom you see to the usual reason. The last two rows are not bugs but behaviour to design around: the registry is eventually consistent, and a registered instance only means the process sent a heartbeat.
Text
Symptom | Most likely cause
--------------------------------------------------|------------------------------------------------------
Spring Boot not compatible with this Cloud train | release train does not match the Boot version
Client gets 403 when registering | CSRF not exempted for /eureka/**
Client gets 401 when registering | wrong or missing credentials in defaultZone (encode special characters)
"Cannot execute request on any known server" | wrong defaultZone, server down, or DNS name not resolvable
Server lists itself as a registered application | register-with-eureka not set to false on a standalone server
Peer shown under "unavailable-replicas" | eureka.instance.hostname does not match the host in the other peer's defaultZone
Instances show hostnames nobody can resolve | set prefer-ip-address=true or a resolvable eureka.instance.hostname
Service shows UP while its database is down | eureka.client.healthcheck.enabled is not true
Red "EMERGENCY" banner on the dashboard | self-preservation is active (heartbeats dropped below the threshold)
Dead instance still receives traffic for minutes | heartbeat, eviction and cache timings add up (see timing section)When Eureka Is Not the Right Choice
Eureka makes sense when your services run on plain VMs or containers without built-in discovery and you want a registry that Spring applications integrate with out of the box. If you deploy on Kubernetes, the platform already provides service discovery: services get stable DNS names and load balancing from Kubernetes Services, and running an additional registry mostly adds a component to operate. Spring Cloud Kubernetes lets your code use the same discovery abstractions on top of that. Teams that need health-checked discovery together with a key-value store or multi-datacenter support often choose Consul instead. Keep in mind that Eureka only handles discovery; it does not route requests, retry failed calls or stop cascading failures, so you still need load balancing, timeouts and circuit breakers in the services that call each other.
Text
Situation | Reasonable choice
--------------------------------------------------|---------------------------------------------
Spring services on VMs or plain containers | Eureka
Everything runs on Kubernetes | Kubernetes Services (+ Spring Cloud Kubernetes)
Need key-value store or multi-datacenter support | Consul
Need traffic policies, mTLS, retries in infra | a service mesh