Published 2026-07-27 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).
Service Discovery with Eureka and Spring Cloud: A Hands-On Tutorial
Remember the early days of microservices, trying to connect service-A to service-B? You’d hardcode URLs like http://localhost:8081/api/users, which quickly breaks down in production. What happens when service-B scales to multiple instances, or moves to a different IP address? You're in for a world of manual configuration headaches and deployment nightmares. This is where service discovery becomes essential. It’s the mechanism that allows microservices to find each other dynamically. Let's get hands-on with a spring cloud eureka tutorial to wire up dynamic service discovery in your microservices registry using Spring Cloud.
Setting Up Your Eureka Server
First, we need a central registry where all our services can register themselves. This is our Eureka Server. Create a new Spring Boot project and add the spring-cloud-starter-netflix-eureka-server dependency.
<!-- pom.xml for Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Next, enable the Eureka server by annotating your main application class:
// EurekaServerApplication.java
package com.shubhambhati.eurekaserver;
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);
}
}
Configure your application.yml to set the server port and disable self-registration, as this server is the registry itself:
# application.yml for Eureka Server
server:
port: 8761 # Default Eureka port
eureka:
client:
register-with-eureka: false # Server does not register itself
fetch-registry: false # Server does not fetch registry from itself
server:
wait-time-in-ms-for-sync-on-startup: 5000 # Give clients time to register
enable-self-preservation: false # Disable for development, keep enabled for prod
Production Note: For high availability, always run multiple Eureka Server instances, typically a cluster of three. Each server should point to its peers in its serviceUrl.defaultZone configuration. Disabling enable-self-preservation in production could lead to services being prematurely de-registered during network partitions or temporary slowdowns, so enable it carefully. Monitor Eureka's memory footprint; while typically small, a large number of instances can increase it.
Registering Microservices with Eureka Client
Now, let's make our microservices discoverable. For each service you want to register (e.g., a user-service or product-service), add the spring-cloud-starter-netflix-eureka-client dependency.
<!-- pom.xml for Microservice Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Enable the discovery client in your service's main application class:
// UserServiceApplication.java
package com.shubhambhati.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
Configure your application.yml for the service to point to the Eureka Server and define its unique name:
# application.yml for UserService
server:
port: 8080
spring:
application:
name: user-service # This name will be used for discovery
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka # Point to your Eureka Server
instance:
instance-id: ${spring.application.name}:${random.value} # Unique ID for multiple instances
lease-renewal-interval-in-seconds: 5 # How often client sends heartbeats
lease-expiration-duration-in-seconds: 10 # How long server waits without heartbeat
Production Note: The instance-id is crucial when running multiple instances of the same service on the same host or for distinguishing them in the Eureka dashboard. It ensures each instance registers uniquely. Eureka operates on eventual consistency; it takes a short period for changes (like a service registering or de-registering) to propagate. Account for this latency in your system design. Also, ensure your application's health checks are integrated; if a service is unhealthy, Eureka might still show it as available unless configured with Spring Boot Actuator for health monitoring.
Consuming Services with Spring Cloud LoadBalancer
Once your services are registered, others need to find and call them using their service ID instead of hardcoded IPs. Spring Cloud provides LoadBalancerClient and integrates with RestTemplate or WebClient to abstract this process. Add spring-cloud-starter-loadbalancer to your consuming service.
<!-- pom.xml for Consuming Service -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId> <!-- Or spring-boot-starter-web for RestTemplate -->
</dependency>
You can then create a WebClient or RestTemplate bean and use the service ID directly in the URL:
// In your consuming service configuration
package com.shubhambhati.orderservice;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced // Essential for using service names in URLs
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
Now, inject WebClient.Builder and build your WebClient instances:
// In a service class that calls UserService
package com.shubhambhati.orderservice;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class OrderService {
private final WebClient webClient;
public OrderService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("lb://USER-SERVICE").build(); // "USER-SERVICE" is the registered name
}
public Mono<String> getUserDetails(Long userId) {
return webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(String.class); // Assuming user details are a String for simplicity
}
}
Production Note: The @LoadBalanced annotation is key here; it tells Spring Cloud to intercept calls to URLs starting with lb:// and resolve them using the LoadBalancerClient. Always implement circuit breakers (like Resilience4j or Hystrix) around inter-service calls to prevent cascading failures. Monitor p99 latency for these calls; if it spikes, it often indicates an upstream service issue or network bottleneck. Proper timeouts for WebClient or RestTemplate are critical to prevent resource exhaustion.
Common Pitfalls
- Single Point of Failure for Eureka Server: Running only one Eureka server in production leaves you vulnerable if it goes down. Always deploy a cluster (e.g., 3 instances) for high availability.
- Misconfigured
defaultZone: Clients must correctly point to the Eureka server'sserviceUrl.defaultZone. A typo or wrong port means your services won't register. - Ignoring Service Health Checks: If a service registers but isn't healthy (e.g., database connection issues), Eureka will still advertise it. Integrate Spring Boot Actuator health endpoints (
management.endpoint.health.show-details: always) and configure Eureka to use them for more accurate client-side load balancing. - Lack of Circuit Breakers and Retries: Relying purely on service discovery without resilience patterns like circuit breakers (Resilience4j) or retry mechanisms will lead to cascading failures when a downstream service experiences issues.
Conclusion
Service discovery with Eureka and Spring Cloud dramatically simplifies managing microservices deployments. It frees you from hardcoding network locations, allowing your applications to scale and adapt dynamically. By following this guide, you can quickly set up your Eureka server, register your services, and enable dynamic service consumption, bringing much-needed agility to your microservice architecture. Embrace service discovery to build more resilient and scalable systems.
Further Reading
Written by **Shubham Bhati* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on LinkedIn, GitHub, or read more at shubh2-0.github.io.*
Top comments (0)