In today’s world, monolithic applications are rapidly being replaced by microservice architectures — where applications are divided into small, independent services that can scale, deploy, and evolve individually.
In this step-by-step tutorial, we’ll build a fully working microservices ecosystem from scratch using:
- Spring Boot 3.2.x
- Spring Cloud Netflix Eureka (Service Discovery)
- Spring Cloud Gateway (API Gateway)
- Spring Cloud OpenFeign (Inter-Service Communication)
- Maven (Build Tool)
By the end, you’ll have four independent Spring Boot microservices running and talking to each other — no Docker, no complex infra, just clean Java + Spring setup.
Why I Used Microservices Architecture in My Project
In one of my recent projects, our monolithic application started facing several challenges as the business grew.
We had multiple teams working on every small change or release caused deployment dependencies, performance bottlenecks, and testing delays.
To overcome these challenges, we decided to migrate to a Microservices-based architecture using Spring Boot and Spring Cloud.
Key Pain Points in the Monolith:
- Every new feature deployment required full application redeployment.
- The codebase became large and tightly coupled.
- Database queries were shared between multiple modules.
- Downtime affected the entire system, even for small bugs.
Architecture Overview
Our ecosystem will have four Spring Boot projects:
|
Service |
Port |
Role |
|---|---|---|
|
Eureka Server |
8761 |
Service registry — all services register here |
|
Department Service |
8081 |
Provides department data |
|
Employee Service |
8082 |
Provides employee data, calls department service |
|
API Gateway |
8080 |
Single entry point, routes requests to services |
Architecture Diagram
** **
How It Works in Real Time
Scenario 1 — Fetching an Employee’s Department Info
When an HR manager opens an employee profile in the web app:
- Frontend calls
- GET https://api.mycompany.com/employees/1001
- Request hits the API Gateway → routes to Employee Service
- Employee Service fetches employee details (from DB) and calls Department Service (via Feign + Eureka) to get department info.
- Employee Service aggregates both results and sends the response back to the Gateway → client.
- The client sees:
{
"employee": {
"id": 1001,
"name": "Alice Johnson",
"departmentId": 2
},
"department": {
"id": 2,
"name": "Engineering"
}
}
Scenario 2 — Scaling Up
- During appraisal season, Employee Service traffic spikes.
- Since each microservice is independent, you can deploy multiple Employee Service instances (on Kubernetes, ECS, etc.).
- Eureka automatically registers them, and the API Gateway load-balances requests across all.
No config change needed
No downtime
Seamless scaling
Step 1 — Create the Parent Folder
Create one folder to hold all projects:
mkdir spring-microservices-ecosystem
We’ll create four sub-modules:
- eureka-server
- department-service
- employee-service
- api-gateway
Each module will be an independent Spring Boot Maven project.
Step 2 — Setup Eureka Server (Service Registry)
Folder: eureka-server
1️ Create pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.6</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>eureka-server</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.1</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-web</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2️ Create main class
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);
}}
3️ application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false
fetch-registry: false
Run & Verify
Run EurekaServerApplication. Then open
Step 3 — Create Department Service
Folder: department-service
1️ pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.6</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>department-service</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>
<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>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2️ Main class
package com.example.departmentservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class DepartmentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(DepartmentServiceApplication.class, args);}}
3️ Model
package com.example.departmentservice.model;
public class Department {
private Long id;
private String name;
public Department() {}
public Department(Long id, String name) {
this.id = id; this.name = name;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
4️ Controller
package com.example.departmentservice.controller;
import com.example.departmentservice.model.Department;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/departments")
public class DepartmentController {
private static final List<Department> departments = Arrays.asList(
new Department(1L, "Engineering"),
new Department(2L, "HR"),
new Department(3L, "Finance")
);
@GetMapping
public List<Department> getAll() {
return departments;
}
@GetMapping("/{id}")
public Department getById(@PathVariable Long id) {
return departments.stream()
.filter(dep -> dep.getId().equals(id))
.findFirst()
.orElse(null);
}
}
5️ application.yml
server:
port: 8081
spring:
application:
name: department-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
Step 4 — Create Employee Service (Feign Client + Eureka)
Folder: employee-service
1️pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.6</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>employee-service</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2️ Main class
package com.example.employeeservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class EmployeeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeServiceApplication.class, args);
}
}
3️ Feign client
package com.example.employeeservice.feign;
import com.example.employeeservice.model.Department;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "department-service")
public interface DepartmentClient {
@GetMapping("/departments/{id}")
Department getDepartmentById(@PathVariable("id") Long id);
}
4️ Models & Controller
package com.example.employeeservice.model;
public class Employee {
private Long id;
private String name;
private Long departmentId;
public Employee() {}
public Employee(Long id, String name, Long departmentId) {
this.id = id; this.name = name; this.departmentId = departmentId;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getDepartmentId() { return departmentId; }
public void setDepartmentId(Long departmentId) { this.departmentId = departmentId; }
}
package com.example.employeeservice.model;
public class Department {
private Long id;
private String name;
public Department() {}
public Department(Long id, String name) { this.id = id; this.name = name; }
public Long getId() { return id; } public void setId(Long id) { this.id = id; }
public String getName() { return name; } public void setName(String name) { this.name = name; }
}
package com.example.employeeservice.controller;
import com.example.employeeservice.feign.DepartmentClient;
import com.example.employeeservice.model.Department;
import com.example.employeeservice.model.Employee;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final DepartmentClient departmentClient;
public EmployeeController(DepartmentClient departmentClient) {
this.departmentClient = departmentClient;
}
private static final List<Employee> employees = Arrays.asList(
new Employee(1L, "Alice", 1L),
new Employee(2L, "Bob", 2L),
new Employee(3L, "Charlie", 1L)
);
@GetMapping
public List<Employee> getAll() { return employees; }
@GetMapping("/{id}")
public Map<String, Object> getEmployeeWithDepartment(@PathVariable Long id) {
Employee emp = employees.stream().filter(e -> e.getId().equals(id)).findFirst().orElse(null);
if (emp == null) return Map.of("error", "Employee not found");
Department dept = departmentClient.getDepartmentById(emp.getDepartmentId());
return Map.of("employee", emp, "department", dept);
}
}
5️ application.yml
server:
port: 8082
spring:
application:
name: employee-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
Step 5 — Create API Gateway
Folder: api-gateway
1️ pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.6</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>api-gateway</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2️ Main class
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3️ application.yml
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: employee-service
uri: lb://EMPLOYEE-SERVICE
predicates:
- Path=/employees/**
- id: department-service
uri: lb://DEPARTMENT-SERVICE
predicates:
- Path=/departments/**
eureka:
client:
service-url:
Step 6 — Run the Services (Order Matters)
# 1. Start the Eureka server
Run As 🡪 EurekaServerApplication
# 2. Start Department service
Run As 🡪 DepartmentServiceApplication
# 3. Start Employee service
Run As 🡪 EmployeeServiceApplication
# 4. Start API Gateway
Run As 🡪 GatewayApplication
Check the Eureka dashboard:
All three clients should show up as REGISTERED.
Step 7 — Test the APIs
Through Gateway
- GET http://localhost:8080/departments
- GET http://localhost:8080/employees
- GET http://localhost:8080/employees/1
Output:
{
"employee": {
"id": 1,
"name": "Alice",
"departmentId": 1
},
"department": {
"id": 1,
"name": "Engineering"
}
}
Step 8 — Troubleshooting
|
Issue |
Cause |
Solution |
|---|---|---|
|
Duplicate Key Exception: found duplicate key spring |
Two top-level spring blocks in YAML |
Merge them into one |
|
Eureka dashboard empty |
Clients not registered |
Check eureka.client.service-url.defaultZone and network |
|
Feign 404 |
Wrong path or service name |
Ensure @FeignClient(name="department-service") matches |
|
Port conflict |
Already in use |
Change server. port |
|
Gateway 404 |
Wrong route predicates |
Check Path and ensure /departments/** is correct |
Conclusion
You’ve just built a fully functioning microservices system from scratch using Spring Boot 3.2and Spring Cloud.
Each service isindependent, discoverable, and communicatesvia Feign through Eureka.
The API Gateway provides a single unified entry point.
This same architecture is used by real enterprise systems — what you’ve built here is a production-grade foundation.