Building Microservices Architecture with Spring Boot 3

The Pillars of Microservices Architecture

The main challenges in microservices architecture are monitoring, configuration management, and ensuring resilient behavior when services become unavailable. This guide explores the essential components you need to implement when building a production-ready microservices ecosystem.

Modern microservices development in 2025 benefits from Spring Boot 3’s enhancements, including Java 21 support with virtual threads, native image compatibility, and improved observability features. These improvements make Spring Boot the ideal framework for building scalable, high-performance microservices.

Core Components

Service Discovery (Eureka Server)

The fundamental purpose of microservices is to decompose applications into independently scalable components organized by business capabilities. Since each microservice can have multiple instances that are dynamically added or removed, they operate with dynamic IP addresses. A service registry is essential for tracking these instances.

The service discovery pattern provides several key benefits:

  • Dynamic Service Registration: Services automatically register themselves when they start
  • Health Monitoring: Tracks which service instances are healthy and available
  • Load Distribution: Enables client-side load balancing across multiple instances
  • Decoupling: Services communicate through logical names rather than hardcoded addresses

For service discovery, Spring Cloud provides Netflix Eureka-based registry support with minimal configuration, though you can also use alternatives like Consul or platform-native solutions such as Kubernetes Services.

Configuration Server

Centralizing configuration management is critical in microservices architecture. A dedicated configuration server:

  • Stores configuration files in a version-controlled repository (typically Git)
  • Exposes configurations to all microservices through REST endpoints
  • Enables dynamic configuration updates without service restarts
  • Separates environment-specific settings from application code

While Spring Cloud Config Server remains a viable option, Kubernetes-native approaches using ConfigMaps and Secrets are increasingly popular for cloud-native deployments, as they reduce coupling and align better with 12-factor app principles.

API Gateway

Spring Cloud Gateway has emerged as the modern choice for API gateways in 2025, offering superior performance through its reactive, non-blocking architecture built on Project Reactor. The gateway provides:

  • Single Entry Point: Unified interface for all client requests
  • Dynamic Routing: Intelligent request routing based on paths, headers, or other criteria
  • Cross-Cutting Concerns: Centralized handling of authentication, rate limiting, and logging
  • Protocol Translation: Support for WebSockets, HTTP/2, and long-lived connections
  • Load Balancing: Distribution of traffic across service instances

Netflix’s Zuul has been deprecated and is no longer maintained. Spring Cloud Gateway is the recommended replacement, providing better throughput, lower latency, and native Kubernetes support.

Building Your Microservices

Technology Stack

For this tutorial, we’ll use:

  • Spring Boot 3.x (compatible with Java 17+)
  • Spring Cloud 2023.x (latest release train)
  • Gradle or Maven for build management
  • Docker for containerization
  • Kubernetes ConfigMaps or Spring Cloud Config for configuration management

All projects will be generated using Spring Initializr.

1. Eureka Server Setup

Create a new Spring Boot project with the following dependencies:

  • Eureka Server (spring-cloud-starter-netflix-eureka-server)
  • Spring Boot Actuator (spring-boot-starter-actuator)

Main Application Class

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

Configuration (application.yml)

spring:
  application:
    name: eureka-server

server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    enable-self-preservation: false  # Disable in development

Key Configuration Notes:

  • register-with-eureka: false - Prevents the server from registering itself as a client
  • fetch-registry: false - Server doesn’t need to fetch registry information
  • Port 8761 is the conventional Eureka server port

Launch the server and navigate to http://localhost:8761 to view the Eureka dashboard showing registered services.

2. Configuration Management

There are two primary approaches for configuration management in 2025:

Option A: Spring Cloud Config Server

Create a Git repository for your configuration files:

mkdir config-repo
cd config-repo
git init

# Create configuration files
cat > eureka-server.yml <<EOF
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
EOF

git add .
git commit -m "Initial configuration"

Create a Config Server project with the spring-cloud-config-server dependency:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

Configure the server to point to your Git repository:

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-org/config-repo
          # For local testing:
          # uri: file://${user.home}/config-repo
          default-label: main
          clone-on-start: true

server:
  port: 8888

If deploying to Kubernetes, use native ConfigMaps and Secrets:

apiVersion: v1
kind: ConfigMap
metadata:
  name: eureka-server-config
data:
  application.yml: |
    server:
      port: 8761
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: false

Add the Spring Cloud Kubernetes dependency to consume ConfigMaps:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>

3. Building Business Microservices

Create microservices for your domain logic. Here’s an example REST service:

User Service

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping
    public ResponseEntity<List<User>> getUsers() {
        // Your business logic here
        return ResponseEntity.ok(userList);
    }
}

Configuration (application.yml)

spring:
  application:
    name: user-service

server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

4. API Gateway with Spring Cloud Gateway

Create a new project with these dependencies:

  • Gateway (spring-cloud-starter-gateway)
  • Eureka Discovery Client (spring-cloud-starter-netflix-eureka-client)
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

Gateway Configuration

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=0
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**

server:
  port: 8080

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

Gateway Features:

  • lb:// prefix enables client-side load balancing via Spring Cloud LoadBalancer
  • Route predicates match incoming requests to backend services
  • Filters transform requests and responses
  • Automatic service discovery through Eureka

Advanced Patterns

Circuit Breaker with Resilience4j

Spring Cloud now uses Resilience4j for circuit breaker patterns, replacing the deprecated Netflix Hystrix. Add fault tolerance to prevent cascade failures:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

Configure circuit breakers in your gateway:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users

resilience4j:
  circuitbreaker:
    instances:
      userServiceCircuitBreaker:
        sliding-window-size: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10000

Distributed Tracing

Enable observability with distributed tracing:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
management:
  tracing:
    sampling:
      probability: 1.0
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans

API Rate Limiting

Protect your services with rate limiting:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

Docker Deployment

Docker Compose Setup

Create a docker-compose.yml to orchestrate all services:

version: '3.8'

services:
  eureka-server:
    build: ./eureka-server
    ports:
      - "8761:8761"
    networks:
      - microservices-network

  config-server:
    build: ./config-server
    ports:
      - "8888:8888"
    depends_on:
      - eureka-server
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    networks:
      - microservices-network

  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - eureka-server
      - config-server
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
      - SPRING_CLOUD_CONFIG_URI=http://config-server:8888
    networks:
      - microservices-network

  user-service:
    build: ./user-service
    depends_on:
      - eureka-server
      - config-server
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
      - SPRING_CLOUD_CONFIG_URI=http://config-server:8888
    networks:
      - microservices-network
    deploy:
      replicas: 2

networks:
  microservices-network:
    driver: bridge

Individual Dockerfile

FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

For native images with GraalVM:

FROM ghcr.io/graalvm/native-image:latest AS builder

WORKDIR /build
COPY . .
RUN ./mvnw -Pnative native:compile

FROM alpine:latest
COPY --from=builder /build/target/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]

Best Practices for 2025

1. Embrace Reactive Programming

Spring Boot 3 fully supports Java 21 Virtual Threads (Project Loom), dramatically improving concurrency by allowing millions of lightweight threads with minimal memory overhead.

Enable virtual threads:

spring:
  threads:
    virtual:
      enabled: true

2. Use Platform-Native Features

When deploying to Kubernetes:

  • Use ConfigMaps/Secrets instead of Config Server
  • Leverage Kubernetes Services for service discovery
  • Utilize liveness and readiness probes
  • Implement horizontal pod autoscaling

3. Implement Comprehensive Observability

  • Metrics: Micrometer with Prometheus
  • Logging: Structured logging with correlation IDs
  • Tracing: Distributed tracing with Zipkin or Jaeger
  • Health Checks: Actuator endpoints for monitoring

4. Security First

  • Implement OAuth2/OIDC for authentication
  • Use API keys or JWT tokens
  • Enable HTTPS/TLS everywhere
  • Regular security audits and dependency updates

5. Database per Service Pattern

Each microservice should own its data:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/userdb
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

Testing Your Microservices

Integration Testing

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class UserServiceIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    void testGetUsers() {
        ResponseEntity<List> response = restTemplate.getForEntity(
            "/api/users", 
            List.class
        );
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }
}

Contract Testing

Use Spring Cloud Contract for consumer-driven contract testing:

Contract.make {
    description "should return user by id"
    request {
        method GET()
        url("/api/users/1")
    }
    response {
        status 200
        body([
            id: 1,
            name: "John Doe"
        ])
        headers {
            contentType(applicationJson())
        }
    }
}

Monitoring and Operations

Actuator Endpoints

Enable comprehensive health checks:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
  health:
    readiness-state:
      enabled: true
    liveness-state:
      enabled: true

Prometheus Metrics

management:
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}

Conclusion

Microservices architecture remains the backbone of modern application development in 2025, with Spring Boot 3 providing the essential tools for building scalable, resilient services. By implementing proper service discovery, configuration management, and API gateway patterns, along with modern observability and resilience features, you can build production-ready microservices that scale with your business needs.

The key to success is starting small, learning from experience, and gradually evolving your architecture. Remember that microservices aren’t a silver bullet—ensure the benefits outweigh the added complexity for your specific use case.

Additional Resources


This article has been updated for Spring Boot 3.x and Spring Cloud 2023.x with current best practices for 2025.