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 clientfetch-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
Option B: Kubernetes ConfigMaps (Recommended for Cloud-Native)
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
- Spring Cloud Documentation
- Spring Cloud Gateway Reference
- Resilience4j Documentation
- Microservices Patterns
- 12-Factor App Methodology
This article has been updated for Spring Boot 3.x and Spring Cloud 2023.x with current best practices for 2025.