Microservices patterns with Spring Boot — service discovery, API gateway, circuit breakers, distributed tracing, event-driven communication with Kafka, and containerization with Docker. Includes Spring Cloud integration.
You are a senior Java architect building distributed systems. Follow these patterns for microservices.
Each service owns its data and exposes a well-defined API. Never share databases between services.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User Service │ │ Order Service│ │ Product Svc │
│ (users DB) │ │ (orders DB) │ │(products DB)│
└──────┬───────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────┴──────┐
│ API Gateway │
└─────────────┘
Rules for splitting services:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r
.path("/api/v1/users/**")
.filters(f -> f
.circuitBreaker(c -> c
.setName("userServiceCB")
.setFallbackUri("forward:/fallback/users"))
.retry(retryConfig -> retryConfig
.setRetries(3)
.setBackoff(Duration.ofMillis(100), Duration.ofSeconds(1), 2, true)))
.uri("lb://user-service"))
.route("order-service", r -> r
.path("/api/v1/orders/**")
.uri("lb://order-service"))
.build();
}
}
// Discovery Server
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
// Client Service — registers automatically
// application.yml
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
Prevent cascade failures when a downstream service is down.
@Service
@RequiredArgsConstructor
public class OrderService {
private final ProductClient productClient;
@CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
@TimeLimiter(name = "productService")
@Retry(name = "productService")
public CompletableFuture<ProductResponse> getProduct(String productId) {
return CompletableFuture.supplyAsync(() ->
productClient.getProduct(productId));
}
private CompletableFuture<ProductResponse> getProductFallback(
String productId, Throwable t) {
log.warn("Product service unavailable for {}: {}", productId, t.getMessage());
return CompletableFuture.completedFuture(
ProductResponse.unavailable(productId));
}
}
# application.yml
resilience4j:
circuitbreaker:
instances:
productService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 3
timelimiter:
instances:
productService:
timeout-duration: 3s
retry:
instances:
productService:
max-attempts: 3
wait-duration: 500ms
Use events for async operations that don't need immediate responses.
// Producer — Order Service
@Service
@RequiredArgsConstructor
public class OrderEventPublisher {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
public void publishOrderCreated(Order order) {
OrderEvent event = OrderEvent.builder()
.eventType("ORDER_CREATED")
.orderId(order.getId())
.userId(order.getUserId())
.items(order.getItems())
.total(order.getTotal())
.timestamp(Instant.now())
.build();
kafkaTemplate.send("order-events", order.getId(), event)
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("Failed to publish order event: {}", order.getId(), ex);
}
});
}
}
// Consumer — Notification Service
@Component
@RequiredArgsConstructor
public class OrderEventConsumer {
private final NotificationService notificationService;
@KafkaListener(
topics = "order-events",
groupId = "notification-service",
containerFactory = "kafkaListenerContainerFactory")
public void handleOrderEvent(OrderEvent event) {
switch (event.getEventType()) {
case "ORDER_CREATED" -> notificationService.sendOrderConfirmation(
event.getUserId(), event.getOrderId());
case -> notificationService.sendShipmentNotification(
event.getUserId(), event.getOrderId());
}
}
}
Track requests across services for debugging.
<!-- pom.xml -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 100% in dev, lower in prod
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
// Declarative HTTP client (Spring 6+)
@HttpExchange("/api/v1/products")
public interface ProductClient {
@GetExchange("/{id}")
ProductResponse getProduct(@PathVariable String id);
@GetExchange
List<ProductResponse> getProducts(@RequestParam List<String> ids);
}
@Configuration
public class ClientConfig {
@Bean
public ProductClient productClient(
@Value("${services.product.url}") String baseUrl) {
WebClient webClient = WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
return HttpServiceProxyFactory
.builderFor(WebClientAdapter.create(webClient))
.build()
.createClient(ProductClient.class);
}
}
# Multi-stage build for small images
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker-compose.yml
services:
user-service:
build: ./user-service
ports: ["8081:8080"]
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/users
EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://discovery:8761/eureka/
depends_on: [postgres, discovery]
order-service:
build: ./order-service
ports: ["8082:8080"]
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/orders
SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
depends_on: [postgres, discovery, kafka]
discovery:
build: ./discovery-server
ports: ["8761:8761"]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
volumes: [pgdata:/var/lib/postgresql/data]
kafka:
image: confluentinc/cp-kafka:7.6.0
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: