Spring Boot Microservices Architecture — API Gateway, Eureka, Config Server & Resilience4j

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-service and order-service
  • Feign Clients + Resilience4j for inter-service calls with circuit breaker
  • MySQL as database (Oracle config as alternative)
After this tutorial, you will have a working microservices system that you can run locally, extend with more services, or deploy to Docker/Kubernetes.

1. Architecture Overview

We’ll build the following components:

ServicePortDescription
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
Traffic flow: Client → API Gateway → Eureka → Product/Order Services. Config for all services is provided by Config Server.

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/
With 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

  1. Start MySQL (and create product_db, order_db)
  2. Start config-server
  3. Start discovery-server
  4. Start product-service
  5. Start order-service
  6. 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
For production, you can integrate Prometheus + Grafana or OpenTelemetry for full observability.

10. Common Pitfalls in Microservices (and How to Avoid Them)

ProblemCauseFix
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.