Spring Boot Microservices Architecture — API Gateway, Eureka, Config Server & Resilience4j (Complete Guide)
Microservices are more than “many small services”. A real production-ready microservices system needs: service discovery, centralized configuration, an API gateway, fault tolerance, and observability.
In this step-by-step guide, we’ll build a complete Spring Boot 3 microservices architecture using:
- Spring Cloud Gateway as API Gateway
- Eureka Server for service discovery
- Config Server for centralized configuration
- Two business services:
product-serviceandorder-service - Feign Clients + Resilience4j for inter-service calls with circuit breaker
- MySQL as database (Oracle config as alternative)
1. Architecture Overview
We’ll build the following components:
| Service | Port | Description |
|---|---|---|
config-server |
8888 | Centralized configuration from Git |
discovery-server |
8761 | Eureka server for service registry |
api-gateway |
8080 | Entry point to all microservices (routes, security, etc.) |
product-service |
8081 | Manages products (CRUD) with MySQL |
order-service |
8082 | Creates orders by calling product-service via Feign |
2. Tech Stack
- Java 17+ / 21
- Spring Boot 3.x
- Spring Cloud (Gateway, Eureka, Config, OpenFeign)
- MySQL (Oracle alternative config)
- Resilience4j for circuit breaker / retry
3. Config Server (Centralized Configuration)
We will keep all application configurations (ports, DB URLs, service names, etc.) in a Git repository and use Spring Cloud Config Server so all services can fetch their config from one place.
3.1. Create config-server project
Dependencies: Spring Cloud Config Server, Spring Web, Actuator.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
3.2. Enable Config Server
package com.example.configserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
3.3. application.yml for Config Server
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/your-username/microservices-config-repo
default-label: main
clone-on-start: true
management:
endpoints:
web:
exposure:
include: health,info
3.4. Example Git repo structure
microservices-config-repo/
application.yml
product-service.yml
order-service.yml
api-gateway.yml
discovery-server.yml
We will see sample content for these files when we configure each service.
4. Discovery Server (Eureka)
Eureka acts as a service registry, where all microservices register themselves. The API Gateway will use Eureka to discover service instances.
4.1. Create discovery-server project
<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-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
4.2. Enable Eureka Server
package com.example.discoveryserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
4.3. application.yml (from config repo)
server:
port: 8761
spring:
application:
name: discovery-server
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
wait-time-in-ms-when-sync-empty: 0
Once you run it and go to http://localhost:8761 you’ll see the Eureka dashboard.
5. Product Service (Product Catalog Microservice)
This service exposes product APIs and stores data in MySQL.
5.1. 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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Oracle alternative (commented) -->
<!--
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<scope>runtime</scope>
</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-actuator</artifactId>
</dependency>
</dependencies>
5.2. bootstrap.yml (use Config Server)
spring:
application:
name: product-service
cloud:
config:
uri: http://localhost:8888
fail-fast: true
5.3. product-service.yml in config repo
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/product_db?useSSL=false&serverTimezone=UTC
username: root
password: your_password
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Oracle alternative:
spring:
datasource:
url: jdbc:oracle:thin:@localhost:1521/ORCLCDB
username: PRODUCT_USER
password: your_password
jpa:
database-platform: org.hibernate.dialect.OracleDialect
5.4. Entity, Repository, Controller
package com.example.productservice.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "products")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Double price;
private Integer availableQuantity;
}
package com.example.productservice.repository;
import com.example.productservice.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
package com.example.productservice.controller;
import com.example.productservice.entity.Product;
import com.example.productservice.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductRepository productRepository;
@PostMapping
public ResponseEntity<Product> create(@RequestBody Product product) {
return ResponseEntity.ok(productRepository.save(product));
}
@GetMapping
public ResponseEntity<List<Product>> findAll() {
return ResponseEntity.ok(productRepository.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<Product> findById(@PathVariable Long id) {
return productRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
6. Order Service (Calls Product Service via Feign + Resilience4j)
The order-service will call product-service to validate stock before placing an order.
We’ll use OpenFeign for HTTP calls and Resilience4j for circuit breaker.
6.1. 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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
6.2. bootstrap.yml
spring:
application:
name: order-service
cloud:
config:
uri: http://localhost:8888
fail-fast: true
6.3. order-service.yml in config repo
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
username: root
password: your_password
jpa:
hibernate:
ddl-auto: update
show-sql: true
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
resilience4j:
circuitbreaker:
instances:
productService:
sliding-window-size: 5
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
6.4. Enable Feign Clients
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
6.5. Feign Client to Product Service
package com.example.orderservice.client;
import lombok.Data;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/api/products/{id}")
ProductResponse getProduct(@PathVariable("id") Long id);
@Data
class ProductResponse {
private Long id;
private String name;
private Double price;
private Integer availableQuantity;
}
}
6.6. Order Entity + Repository
package com.example.orderservice.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Integer quantity;
private Double totalPrice;
private String status; // CREATED, FAILED
}
package com.example.orderservice.repository;
import com.example.orderservice.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
6.7. Service with Circuit Breaker
package com.example.orderservice.service;
import com.example.orderservice.client.ProductClient;
import com.example.orderservice.entity.Order;
import com.example.orderservice.repository.OrderRepository;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class OrderService {
private final ProductClient productClient;
private final OrderRepository orderRepository;
@CircuitBreaker(name = "productService", fallbackMethod = "createOrderFallback")
public Order createOrder(Long productId, Integer quantity) {
ProductClient.ProductResponse product = productClient.getProduct(productId);
if (product.getAvailableQuantity() < quantity) {
throw new IllegalArgumentException("Not enough stock");
}
double totalPrice = product.getPrice() * quantity;
Order order = Order.builder()
.productId(productId)
.quantity(quantity)
.totalPrice(totalPrice)
.status("CREATED")
.build();
return orderRepository.save(order);
}
public Order createOrderFallback(Long productId, Integer quantity, Throwable t) {
// We can store a FAILED order or return a dummy object
Order failedOrder = Order.builder()
.productId(productId)
.quantity(quantity)
.status("FAILED")
.totalPrice(0.0)
.build();
return orderRepository.save(failedOrder);
}
}
6.8. Order Controller
package com.example.orderservice.controller;
import com.example.orderservice.entity.Order;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<Order> create(@RequestParam Long productId,
@RequestParam Integer quantity) {
return ResponseEntity.ok(orderService.createOrder(productId, quantity));
}
}
7. API Gateway (Spring Cloud Gateway)
API Gateway is the single entry point to all microservices. It handles routing, cross-cutting concerns like security, rate limiting, logging, etc.
7.1. Dependencies
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
7.2. bootstrap.yml
spring:
application:
name: api-gateway
cloud:
config:
uri: http://localhost:8888
fail-fast: true
7.3. api-gateway.yml in config repo
server:
port: 8080
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: product-service
uri: lb://product-service
predicates:
- Path=/product-service/**, /api/products/**
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/order-service/**, /api/orders/**
filters:
- StripPrefix=1
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
discovery.locator.enabled=true, the gateway can route requests to services
registered in Eureka using lb://service-name.
8. Start Order & Testing Flow
8.1. Startup order
- Start MySQL (and create
product_db,order_db) - Start config-server
- Start discovery-server
- Start product-service
- Start order-service
- Start api-gateway
8.2. Create a product via API Gateway
curl -X POST "http://localhost:8080/api/products" \
-H "Content-Type: application/json" \
-d '{
"name": "Laptop",
"price": 75000,
"availableQuantity": 10
}'
8.3. List products
curl "http://localhost:8080/api/products"
8.4. Create an order
curl -X POST "http://localhost:8080/api/orders?productId=1&quantity=2"
Internally, the order-service calls product-service via Feign through Eureka and applies Resilience4j circuit breaker.
9. Observability & Health Checks
Enable basic actuator endpoints in each service to monitor health and metrics:
management:
endpoints:
web:
exposure:
include: health,info,metrics
You can then call:
curl http://localhost:8081/actuator/health # product-service
curl http://localhost:8082/actuator/health # order-service
curl http://localhost:8080/actuator/health # gateway
10. Common Pitfalls in Microservices (and How to Avoid Them)
| Problem | Cause | Fix |
|---|---|---|
| Random connection timeouts between services | No timeout / retry / circuit breaker | Use Resilience4j with sensible defaults per client |
| Config changes don’t apply | Services not refreshed | Use Spring Cloud Bus / manual refresh or restart |
| Gateway returns 404 for valid paths | Wrong route predicates / StripPrefix config | Double-check route paths and filters |
| Hard-coded URLs in services | Not using service discovery | Always use lb://service-name and Feign clients |
| Database schema conflicts | Sharing a single DB between many services | Prefer “database per service” or clear boundaries |
11. When to Use Microservices (and When Not To)
- Use microservices when:
- Different parts of the system scale independently (e.g., product catalog vs billing)
- You have multiple teams owning different business capabilities
- You need different tech stacks for different modules
- Prefer a modular monolith when:
- Team is small, domain is not too large
- You don’t have strong operational maturity yet (monitoring, CI/CD, infra)
- You want simplicity and speed initially
The architecture we built is a good template when you decide the complexity is worth it — but you can also start as a monolith and gradually move to this model.
12. Summary
In this guide, we designed and implemented a complete Spring Boot microservices architecture:
- Centralized configuration with Spring Cloud Config Server
- Service discovery with Eureka
- Routing & edge concerns with Spring Cloud Gateway
- Two business services: product-service and order-service with MySQL
- Inter-service communication with OpenFeign
- Fault tolerance using Resilience4j circuit breaker
- Basic observability via Actuator
You can now extend this blueprint by adding:
- Authentication / authorization (e.g., JWT from your existing auth blog)
- Centralized logging and tracing (Zipkin / OpenTelemetry)
- Docker + Kubernetes deployment
- More microservices (inventory, payment, notification, etc.)
This post is designed as a reference architecture you can come back to whenever you build new microservices projects with Spring Boot.