DEV Community

Shubham Bhati
Shubham Bhati

Posted on

Service Discovery with Eureka and Spring Cloud: A Hands-On Tutorial

Spring Cloud Eureka Tutorial

Published 2026-07-29 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).

Service Discovery with Eureka and Spring Cloud: A Hands-On Tutorial

By Shubham Bhati, Backend Engineer (Java 17, Spring Boot, Microservices)

Imagine your microservices architecture growing from a handful to dozens. How do these services find each other? Hardcoding IP addresses is fragile. Manually updating DNS records for every new deployment is a maintenance nightmare, especially when dealing with scaling instances or failures. That's a production problem begging for a robust solution. This is where service discovery shines. In this spring cloud eureka tutorial, we'll cut through the complexity and get our microservices registered, discovered and communicating using Spring Cloud Netflix Eureka. Let's make our services intelligent enough to find their friends.

Setting Up Your Eureka Server

The Eureka Server acts as a centralized registry where all your microservices register themselves. It's the brain of your service discovery setup. When other services need to find an instance of a particular service, they query the Eureka Server. Running this server usually means dedicating a small VM or container. For production, consider at least two instances for high availability. While memory footprint is typically low for just the server, monitor its network I/O and CPU as client registrations scale.

First, create a new Spring Boot project and add the spring-cloud-starter-netflix-eureka-server dependency.

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Next, enable the Eureka server in your main application class:

// EurekaServerApplication.java
package com.example.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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, configure your application.yml to disable client registration for the server itself and specify the port.

# application.yml for Eureka Server
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false # Eureka Server doesn't register itself
    fetch-registry: false       # Eureka Server doesn't fetch registry from itself
  server:
    wait-time-in-ms-for-sync: 0 # Faster startup in dev, adjust for prod
Enter fullscreen mode Exit fullscreen mode

Registering Your Microservice with Eureka Client

Now that our Eureka Server is ready, let's get a microservice to register itself. This service, often called a Eureka client, will periodically send heartbeats to the server, informing it of its availability. For a production service, ensure consistent spring.application.name values across all instances of a service. Health check configurations are crucial; a slow or failing health check could cause Eureka to prematurely de-register an otherwise healthy service, affecting p99 latency for upstream calls. Consider custom health indicators for deep service health rather than just basic HTTP checks.

First, add the spring-cloud-starter-netflix-eureka-client dependency to your microservice's pom.xml.

<!-- pom.xml for Microservice -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Enable the discovery client in your microservice's main application class:

// MyServiceApplication.java
package com.example.myservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class MyServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyServiceApplication.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

Configure your microservice's application.yml with its name and the Eureka server URL:

# application.yml for Microservice
server:
  port: 8080 # or any other port

spring:
  application:
    name: my-first-service # This name is key for discovery

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka # Point to your Eureka Server
Enter fullscreen mode Exit fullscreen mode

Consuming Services via DiscoveryClient and Load Balancing

With services registered, the next step is calling them. We don't want to use hardcoded IPs anymore. Spring Cloud provides DiscoveryClient and LoadBalancerClient (which RestTemplate with @LoadBalanced uses) to fetch service instances and distribute requests. When using RestTemplate with @LoadBalanced, a proxy intervenes to resolve service names to actual instances and handle client-side load balancing. For optimal production performance, consider configuring HikariCP connection pooling for your RestTemplate or WebClient beans to reduce connection overhead and latency, especially during high request volumes.

Here's how you can inject DiscoveryClient to manually find services:

// SomeServiceConsumer.java
package com.example.consumerservice;

import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class SomeServiceConsumer {

    private final DiscoveryClient discoveryClient;

    public SomeServiceConsumer(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    public String callMyFirstService() {
        List<String> serviceUrls = discoveryClient.getInstances("my-first-service")
            .stream()
            .map(si -> si.getUri().toString())
            .toList(); // Java 17 .toList()

        // In a real scenario, you'd pick an instance and make an HTTP call
        if (!serviceUrls.isEmpty()) {
            return "Found 'my-first-service' at: " + serviceUrls.get(0);
        }
        return "Service 'my-first-service' not found.";
    }
}
Enter fullscreen mode Exit fullscreen mode

For simpler HTTP calls with client-side load balancing, use @LoadBalanced on your RestTemplate or WebClient bean:

// MyServiceConsumerApplication.java
package com.example.consumerservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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;

@SpringBootApplication
@EnableDiscoveryClient
public class MyServiceConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyServiceConsumerApplication.class, args);
    }

    @Bean
    @LoadBalanced // Essential for service name resolution
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, you can use the RestTemplate like this:

// MyServiceClient.java
package com.example.consumerservice;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyServiceClient {

    private final RestTemplate restTemplate;

    public MyServiceClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String getGreetingFromMyFirstService() {
        // Use the service name instead of host:port
        return restTemplate.getForObject("http://my-first-service/greeting", String.class);
    }
}
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls

  • Firewall or Network Issues: Ensure your Eureka Server and clients can communicate on the configured ports. AWS Security Groups or Docker network configurations are common culprits.
  • Incorrect service-url: Mismatched defaultZone in client configuration pointing to the wrong Eureka Server address or port.
  • Missing @EnableDiscoveryClient / @EnableEurekaServer: Forget these annotations and your application won't behave as a client or server.
  • Misconfigured spring.application.name: Each microservice instance needs a consistent spring.application.name to be discovered correctly.
  • Eureka Server Self-Registration: Accidentally allowing the Eureka server to try registering with itself, leading to errors or warnings.

Conclusion

Service discovery with Spring Cloud Eureka provides a reliable, dynamic way for your microservices to find each other, addressing a critical challenge in distributed systems. We've walked through setting up a Eureka Server, registering client services and consuming them using intelligent client-side load balancing. By understanding these core concepts and keeping production concerns in mind, you're well-equipped to build more resilient and scalable microservice applications. Start integrating Eureka today and simplify your microservice communication!


Spring Cloud Eureka Tutorial in production

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)