Building Service Registration and Discovery with Spring Boot and Spring Cloud Eureka
Overview
Eureka, created by Netflix, is a registry for service discovery in microservice architectures. Each provider registers itself with Eureka, while consumers query Eureka to locate instances. Although Netflix has ended active maintenance of Eureka 2.x, the Eureka 1.x implementation in Spring Cloud remains widely used and stable in production. Alternatives like Nacos and Consul offer broader capabilities, but this guide focuses on a minimal Eureka-based setup.
This example builds a small system:
- Two Eureka Server nodes (ports 7001 and 7002) forming a cluster
- Two provider instances (ports 8001 and 8002) registered to Eureka
- One consumer (port 8087) that discovers and calls the providers with client-side load balancing
Version Alignment
Spring Cloud versions are tight coupled with Spring Boot. For a Hoxton-based stack, use Spring Boot 2.2.x. The parent BOM coordinates these versions for all modules.
Parent POM snippet:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Project Layout
Create a multi-module Maven project:
- registry-7001 (Eureka Server node A)
- registry-7002 (Eureka Server node B)
- student-svc-8001 (provider A)
- student-svc-8002 (provider B)
- student-consumer-8087 (consumer)
Eureka Server Cluster
Add a module for an Eureka Server. The second node will reuse the same code with different configuraton.
POM dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Node A configuration (application.yml):
server:
port: 7001
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka-b.local:7002/eureka
instance:
hostname: eureka-a.local
server:
enable-self-preservation: false
Main class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class Registry7001Application {
public static void main(String[] args) {
SpringApplication.run(Registry7001Application.class, args);
}
}
For node B, duplicate the module or profile and change:
- server.port to 7002
- eureka.instance.hostname to eureka-b.local
- eureka.client.service-url.defaultZone to http://eureka-a.local:7001/eureka
Add host entries for local testing:
127.0.0.1 eureka-a.local
127.0.0.1 eureka-b.local
Start both registry nodes and confirm each shows the other under the "DS Replicas" section in the Eureka dashboard.
Provider Service (clustered on 8001/8002)
Create a web service that registers with Eureka. We’ll run two instances with different ports.
POM dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Instance A configuration (application.yml on port 8001):
server:
port: 8001
spring:
application:
name: STUDENT-SVC
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-a.local:7001/eureka,http://eureka-b.local:7002/eureka
instance:
instance-id: student-svc-8001
prefer-ip-address: true
REST controller:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/students")
public class StudentsApi {
@Value("${server.port}")
private String port;
@GetMapping
public List<String> getAll() {
return Arrays.asList("Ada", "Grace", "Linus", "Ken");
}
@GetMapping("/version")
public String version() {
String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
return "port=" + port + ",build=" + ts;
}
}
Main class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class StudentService8001Application {
public static void main(String[] args) {
SpringApplication.run(StudentService8001Application.class, args);
}
}
For instance B, run a second module or process with:
server:
port: 8002
spring:
application:
name: STUDENT-SVC
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka-a.local:7001/eureka,http://eureka-b.local:7002/eureka
instance:
instance-id: student-svc-8002
prefer-ip-address: true
After starting both providers, verify they appear in the registry under the same application name (STUDENT-SVC) with two healthy instances.
Consumer (8087) with Ribbon Load Balancing
Create a consumer that discovers STUDENT-SVC and calls it via service name using a load-balanced RestTemplate.
POM dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Configuration (application.yml):
server:
port: 8087
spring:
application:
name: student-consumer
eureka:
client:
fetch-registry: true
register-with-eureka: false
service-url:
defaultZone: http://eureka-a.local:7001/eureka,http://eureka-b.local:7002/eureka
RestTemplate configuration and consumer controller:
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@Configuration
class ClientHttpConfig {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@RequestMapping("/students")
class StudentClientController {
private final RestTemplate http;
StudentClientController(RestTemplate http) {
this.http = http;
}
@GetMapping("/version")
public String version() {
// Ribbon chooses an instance of STUDENT-SVC and forwards the request
return http.getForObject("http://STUDENT-SVC/students/version", String.class);
}
}
Main class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class StudentConsumer8087Application {
public static void main(String[] args) {
SpringApplication.run(StudentConsumer8087Application.class, args);
}
}
Running the System
- Start registry-7001 and registry-7002
- Start student-svc-8001 and student-svc-8002
- Start student-consumer-8087
- Call http://localhost:8087/students/version repeatedly; you should see alternating outputs reflecting ports 8001 and 8002, demonstrating client-side load balancing