Java Microservices Architecture: From Monolith to Cloud (2025)

The journey from monolithic applications to microservices architecture represents a significant evolution in software development. This comprehensive guide explores the principles, patterns, and practices for building scalable microservices in Java, from initial design to cloud deployment.
Pro Tip: Understanding microservices architecture patterns helps developers build scalable, maintainable, and resilient applications.
Table of Contents
From Monolith to Microservices
Note: Successful migration from monolith to microservices requires careful planning and understanding of domain boundaries.
Domain-Driven Design Example
// Domain Model
public class Order {
private String orderId;
private List items;
private OrderStatus status;
// Domain logic
public void addItem(OrderItem item) {
validateItem(item);
items.add(item);
recalculateTotal();
}
public void complete() {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Order cannot be completed");
}
status = OrderStatus.COMPLETED;
publishOrderCompletedEvent();
}
}
// Service Layer
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final EventPublisher eventPublisher;
@Transactional
public Order createOrder(OrderRequest request) {
Order order = new Order();
// Add items and business logic
order = orderRepository.save(order);
eventPublisher.publishOrderCreated(order);
return order;
}
}
Microservices Design Patterns
Pro Tip: Understanding and applying appropriate design patterns is crucial for building robust microservices.
API Gateway Pattern
@RestController
@RequestMapping("/api/v1")
public class OrderController {
private final OrderService orderService;
@GetMapping("/orders/{orderId}")
public ResponseEntity getOrder(@PathVariable String orderId) {
return ResponseEntity.ok(orderService.getOrder(orderId));
}
@PostMapping("/orders")
public ResponseEntity createOrder(@RequestBody OrderRequest request) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(orderService.createOrder(request));
}
}
// API Gateway Configuration
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("order_service", r -> r
.path("/api/v1/orders/**")
.filters(f -> f
.circuitBreaker(config -> config
.setName("orderCircuitBreaker")
.setFallbackUri("forward:/fallback"))
.retry(config -> config
.setRetries(3)
.setMethods(HttpMethod.GET)))
.uri("lb://order-service"))
.build();
}
}
Spring Cloud Integration
Note: Spring Cloud provides essential tools for building cloud-native microservices.
Service Discovery and Load Balancing
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// Service Configuration
@Configuration
public class ServiceConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient();
}
}
// Service Client
@Service
public class PaymentServiceClient {
private final RestTemplate restTemplate;
public PaymentServiceClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public PaymentResponse processPayment(PaymentRequest request) {
return restTemplate.postForObject(
"http://payment-service/api/v1/payments",
request,
PaymentResponse.class
);
}
}
Service Mesh Architecture
Pro Tip: Service mesh provides advanced networking, security, and observability features for microservices.
Istio Integration Example
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: order-service
spec:
host: order-service
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
Data Management
Note: Proper data management is crucial for maintaining data consistency across microservices.
Event Sourcing Pattern
@Entity
public class OrderEvent {
@Id
private String eventId;
private String orderId;
private String eventType;
private String eventData;
private LocalDateTime timestamp;
}
@Service
public class OrderEventService {
private final OrderEventRepository eventRepository;
private final EventPublisher eventPublisher;
@Transactional
public void saveEvent(OrderEvent event) {
eventRepository.save(event);
eventPublisher.publish(event);
}
public Order reconstructOrder(String orderId) {
List events = eventRepository.findByOrderId(orderId);
Order order = new Order();
events.forEach(event -> applyEvent(order, event));
return order;
}
}
Resilience Patterns
Pro Tip: Implementing resilience patterns ensures microservices can handle failures gracefully.
Circuit Breaker Implementation
@Configuration
public class ResilienceConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(5))
.slidingWindowSize(10)
.build();
return CircuitBreakerRegistry.of(config);
}
}
@Service
public class PaymentService {
private final CircuitBreaker circuitBreaker;
public PaymentService(CircuitBreakerRegistry registry) {
this.circuitBreaker = registry.circuitBreaker("paymentService");
}
public PaymentResponse processPayment(PaymentRequest request) {
return circuitBreaker.run(
() -> callPaymentProvider(request),
throwable -> handlePaymentFailure(throwable)
);
}
}
Deployment Strategies
Note: Effective deployment strategies ensure smooth updates and rollbacks of microservices.
Kubernetes Deployment Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: order-service:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: EUREKA_CLIENT_SERVICEURL_DEFAULTZONE
value: "http://eureka-server:8761/eureka/"
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "500m"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Best Practices Summary
- Use Domain-Driven Design for service boundaries
- Implement appropriate design patterns
- Leverage Spring Cloud for cloud-native features
- Consider service mesh for advanced networking
- Implement proper data management strategies
- Use resilience patterns for fault tolerance
- Follow deployment best practices
- Monitor and observe services effectively
Conclusion
Building microservices in Java requires a comprehensive understanding of architecture patterns, cloud-native practices, and operational considerations. By following these guidelines and best practices, developers can create scalable, maintainable, and resilient microservices that thrive in a cloud environment.