DEV Community

Gaurav
Gaurav

Posted on

Microservices Design Patterns in Java

Microservices Design Patterns in Java

(A practical guide for architects, senior engineers, and teams building cloud‑native systems with Spring Boot, Spring Cloud, Micronaut, Quarkus, and related libraries.)


1️⃣ Why Patterns Matter in a Java Microservices Landscape

Challenge Pattern Category Typical Java Tooling
Service granularity & bounded contexts Decomposition Spring Boot, Micronaut, Quarkus, JPA/Hibernate
Service discovery & routing
Resilience to failures Fault‑tolerance Resilience4j, Spring Cloud CircuitBreaker, Hystrix (legacy)
Data consistency across services Transactional Saga, Event‑sourcing, Outbox
Observability (metrics, logs, traces) Monitoring Micrometer, Spring Cloud Sleuth, OpenTelemetry
Secure communication Security Spring Security, OAuth2 Resource Server, JWT
CI/CD & deployment automation DevOps Docker, Kubernetes, Helm, Skaffold, Tekton

Design patterns give you a vocabulary (e.g., “circuit‑breaker”) and a prescribed solution that can be directly mapped to Java libraries, configuration, and code. Below is a curated catalogue of the most widely‑used patterns, why you’d pick them, and concrete Java snippets.


2️⃣ Decomposition Patterns (How to break the monolith)

Pattern Goal When to Use Java Implementation
Domain‑Driven Design (DDD) Bounded Context Separate business capabilities into autonomous services. Complex domain with clear sub‑domains; need independent evolution. Spring Boot + JPA (separate schema per service), Micronaut Data, Quarkus Panache.
API‑First / Contract‑First Define service contracts (OpenAPI/Swagger) before implementation. Teams need clear contracts, automated client generation. springdoc‑openapi, OpenAPI Generator, Micronaut OpenAPI.
Strangler Fig Incrementally replace a monolith with micro‑services. Legacy monolith, low‑risk migration. Spring Cloud Gateway for routing, Feature Flags (Togglz).
Self‑Contained System (SCS) End‑to‑end ownership (UI + backend) per service. Teams own full stack; UI composition needed (e.g., micro‑frontends). Spring Boot + Thymeleaf or React built into the same Docker image.

Tip: In Java, the bounded context often maps to a module (Maven/Gradle sub‑project) with its own application.yml profile and isolated database schema.


3️⃣ Integration Patterns (How services talk)

Pattern Description Typical Java Stack Code Sketch
API Gateway Single entry point, request routing, cross‑cutting concerns (auth, throttling). Spring Cloud Gateway, Netflix Zuul (legacy), Kong (external).


java @Bean public RouteLocator routes(RouteLocatorBuilder b) { return b.routes() .route("users", r -> r.path("/users/**") .uri("lb://USER-SERVICE")) .build(); }

|
| Service Discovery | Dynamic lookup of service instances. | Eureka, Consul, Zookeeper, Kubernetes DNS. |

yaml spring: application: name: order-service cloud: discovery: client: simple: enabled: true

|
| Client‑Side Load Balancing | Distribute calls across multiple instances. | Spring Cloud LoadBalancer, Ribbon (legacy), Resilience4j's Bulkhead. |

java @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); }

|
| Synchronous Request‑Response (REST/HTTP) | Simple, low‑latency calls. | Spring WebFlux (reactive) or Spring MVC (blocking). |

java @RestController class OrderController { @GetMapping("/orders/{id}") public OrderDto get(@PathVariable Long id) { return orderService.find(id); } }

|
| Asynchronous Messaging | Decouple producers/consumers, enable eventual consistency. | Spring Cloud Stream, Kafka, RabbitMQ, Pulsar, ActiveMQ. |

java @EnableBinding(Source.class) class OrderProducer { @Autowired Source source; void publish(OrderEvent e) { source.output().send(MessageBuilder.withPayload(e).build()); } }

|
| Event‑Driven Architecture (EDA) | React to domain events, enable saga orchestration. | Axon Framework, Eventuate, Kafka Streams, Debezium. |

java @EventHandler public void on(OrderCreated e) { // update read model }

|
| API Composition (Aggregator) | Combine multiple service calls into a single response. | Spring Cloud Function, GraphQL, BFF pattern. |

java @RestController class CustomerBff { @GetMapping("/customer/{id}") public CustomerDto get(@PathVariable Long id) { CustomerDto c = customerClient.get(id); List<OrderDto> o = orderClient.getByCustomer(id); c.setOrders(o); return c; } }

|


4️⃣ Data Management Patterns (How to keep data consistent)

Pattern Goal When to Use Java‑centric Implementation
Database per Service Strong isolation, independent schema evolution. All services own their data. JPA/Hibernate with separate DataSource bean per service.
Shared Database (Anti‑Pattern) Quick start but couples services. Legacy migration phase only. Avoid; instead use Read‑Only Views if needed.
Saga (Transaction Orchestration) Distributed transaction across services without 2PC. Multi‑step business process (e.g., order → payment → inventory). Axon (orchestrated), Eventuate Tram, Camunda BPM.
Saga (Choreography) Services publish events; others react autonomously. Simple flows, want loose coupling. Kafka + Spring Cloud Stream; each service subscribes to its own topic.
Outbox Pattern Guarantees atomic write + message publish. Need reliable event publishing from a DB transaction. Debezium + outbox table; or Spring Transactional outbox library.
CQRS (Command‑Query Responsibility Segregation) Separate write model from read model for scalability. High read‑write imbalance. Axon, Spring Data JDBC for command side, Spring Data JPA + Elasticsearch for query side.
Event Sourcing Persist state as a series of events. Auditable, immutable history required. Axon Framework, EventStoreDB, JPA + EventEntity.
Cache‑Aside / Read‑Through Reduce latency for hot data. Frequently read data, tolerant of slight staleness. Spring Cache (@Cacheable) + Redis or Caffeine.

Sample Saga (Orchestration) using Spring Cloud & Resilience4j

@Service
@RequiredArgsConstructor
public class OrderSaga {

    private final PaymentClient paymentClient;
    private final InventoryClient inventoryClient;
    private final OrderRepository orderRepo;

    @Transactional
    public void start(OrderDto order) {
        OrderEntity ent = orderRepo.save(OrderMapper.toEntity(order));

        // 1️⃣ Reserve inventory (synchronous)
        inventoryClient.reserve(order.getItems())
            .onFailure(throwable -> compensateInventory(ent))
            .retry(3)
            .subscribe();

        // 2️⃣ Charge payment (async with circuit‑breaker)
        paymentClient.charge(order.getPaymentInfo())
            .transform(CircuitBreaker.ofDefaults("paymentCB")::decorateFunction)
            .onSuccess(p -> completeOrder(ent))
            .onFailure(t -> compensatePayment(ent));
    }

    private void compensateInventory(OrderEntity e) {
        inventoryClient.release(e.getId());
        orderRepo.delete(e);
    }

    private void compensatePayment(OrderEntity e) {
        paymentClient.refund(e.getPaymentId());
        // maybe also release inventory
        compensateInventory(e);
    }

    private void completeOrder(OrderEntity e) {
        e.setStatus(Status.COMPLETED);
        orderRepo.save(e);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • Resilience4j provides the circuit‑breaker (CircuitBreaker.ofDefaults).
  • Reactive (Mono/Flux) ensures non‑blocking**.
  • Compensation methods implement the saga’s rollback logic.

5️⃣ Resilience & Fault‑Tolerance Patterns

Pattern Synopsis Java Tools
Circuit Breaker Short‑circuit failing calls; open → close after cooldown. Resilience4j, Spring Cloud CircuitBreaker, Hystrix (legacy).
Retry Re‑attempt transient failures with back‑off. Resilience4j Retry, Spring Retry (@Retryable).
Bulkhead Limit concurrent calls per service to prevent cascading failures. Resilience4j Bulkhead, ThreadPoolBulkhead.
Timeout Fail fast if a remote call exceeds SLA. Resilience4j TimeLimiter, WebClient.timeout.
Fallback Provide a default response when a service is unavailable. fallbackMethod in Feign, @CircuitBreaker(fallbackMethod="...").
Rate Limiting Throttle incoming traffic per client or per endpoint. Bucket4j, Resilience4j RateLimiter, Spring Cloud Gateway RateLimiter.
Idempotent Consumer Ensure message processing is safe against duplicates. Spring Integration IdempotentReceiver, Kafka exactly‑once (enable.idempotence).

Example: Circuit Breaker + Retry with Spring Cloud OpenFeign

@FeignClient(name = "inventory-service", fallback = InventoryFallback.class,
             configuration = InventoryClientConfig.class)
public interface InventoryClient {

    @PostMapping("/inventory/reserve")
    @CircuitBreaker(name = "inventoryCB", fallbackMethod = "reserveFallback")
    @Retry(name = "inventoryRetry", maxAttempts = 3)
    ReservationResponse reserve(@RequestBody ReservationRequest request);
}

// Fallback bean
@Component
class InventoryFallback implements InventoryClient {
    @Override
    public ReservationResponse reserve(ReservationRequest request) {
        // Return a safe “no‑stock” response
        return new ReservationResponse(false, "Service unavailable – using cached data");
    }
}

// Feign config (Resilience4j integration)
@Configuration
class InventoryClientConfig {
    @Bean
    public RetryConfig inventoryRetryConfig() {
        return RetryConfig.custom()
                .maxAttempts(3)
                .waitDuration(Duration.ofMillis(200))
                .retryExceptions(IOException.class, TimeoutException.class)
                .build();
    }

    @Bean
    public CircuitBreakerConfig inventoryCircuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofSeconds(30))
                .permittedNumberOfCallsInHalfOpenState(5)
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Result:

  • Calls to inventory-service are automatically wrapped in a circuit‑breaker and retry policy.
  • If the circuit opens, Feign delegates to InventoryFallback, guaranteeing a graceful degradation.

6️⃣ Observability Patterns (Logging, Metrics, Tracing)

Pattern What It Solves Java Stack
Centralized Logging Correlate logs across services. Logback + logstash‑logback‑encoder, ELK, Grafana Loki, Spring Cloud Sleuth (adds trace IDs).
Distributed Tracing Follow a request across service boundaries. Spring Cloud Sleuth (Zipkin), OpenTelemetry Java, Jaeger.
Metrics & Health Checks Export service‑level KP Micrometer (Prometheus, Datadog, CloudWatch), Spring Boot Actuator.
Structured Log Context Include traceId, spanId, tenantId in every log line. MDC.put("traceId", ...) automatically by Sleuth.
Alerting on SLAs Detect latency spikes, error rates. Prometheus Alertmanager, Grafana, Opsgenie.

Sample: OpenTelemetry + Micrometer (Spring Boot 3)

@SpringBootApplication
public class PaymentService {

    public static void main(String[] args) {
        SpringApplication.run(PaymentService.class, args);
    }

    @Bean
    public OpenTelemetry openTelemetry() {
        return OpenTelemetrySdk.builder()
                .setTracerProvider(SdkTracerProvider.builder()
                        .addSpanProcessor(BatchSpanProcessor.builder(
                                OtlpGrpcSpanExporter.builder()
                                        .setEndpoint("http://otel-collector:4317")
                                        .build())
                                .build())
                        .build())
                .setMeterProvider(SdkMeterProvider.builder()
                        .registerMetricReader(
                                PeriodicMetricReader.builder(
                                        OtlpGrpcMetricExporter)  // e.g., Prometheus exporter
                                        .setInterval(Duration.ofSeconds(30))
                                        .build())
                        .build())
                .build();
    }

    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCustomizer() {
        return registry -> registry.config().commonTags("service", "payment-service");
    }
}
Enter fullscreen mode Exit fullscreen mode

Result:

  • Every HTTP request gets a trace (traceId) automatically via Spring Cloud Sleuth (or otel-spring-boot-starter).
  • Custom Micrometer metrics (payment_success_total, payment_failure_total) are exported to Prometheus.

7️⃣ Security Patterns

Pattern Use‑Case Java Implementation
Zero‑Trust API Gateway Enforce authentication/authorization centrally. Spring Cloud Gateway + Spring Security OAuth2 Resource Server (JwtAuthenticationConverter).
Token‑Based Authentication (JWT) Stateless auth across services. spring-boot-starter-oauth2-resource-server, jjwt, Nimbus JOSE.
Service‑to‑Service Mutual TLS Secure inter‑service traffic. Spring Cloud Consul (or Istio), Spring Security SslContext.
Fine‑Grained Authorization (ABAC) Policy‑driven access (attributes, scopes). OPA (Open Policy Agent) side‑car, Spring Security ACL, Keycloak Authorization Services.
Secret Management Centralized credentials, rotation. Spring Cloud Config + Vault, AWS Secrets Manager, Azure Key Vault.
Security Headers & CSP Harden HTTP responses. Spring Security http.headers().contentSecurityPolicy(...).

Example: JWT Resource Server with Spring Security

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(jwtAuthConverter());
    }

    private JwtAuthenticationConverter jwtAuthConverter() {
        JwtAuthenticationConverter conv = new JwtAuthenticationConverter();
        conv.setJwtGrantedAuthoritiesConverter(jwt -> {
            Collection<String> scopes = jwt.getClaimAsStringList("scope");
            return scopes.stream()
                    .map(s -> new SimpleGrantedAuthority("SCOPE_" + s))
                    .collect(Collectors.toList());
        });
        return conv;
    }
}
Enter fullscreen mode Exit fullscreen mode

All microservices that include this configuration will automatically validate incoming JWTs signed by the Authorization Server (Keycloak, Auth0, Okta, etc.).


8️⃣ DevOps & Deployment Patterns

Pattern Description Java‑centric Tooling
Docker‑Ready Build Produce an OCI image with minimal layers. Spring Boot 3java -jar + jlink for native images, Micronaut GraalVM Native, Quarkus native mode.
Kubernetes Sidecar Add capabilities (e.g., Envoy proxy, Prometheus exporter). Spring Cloud Kubernetes, Istio, Envoy, OpenTelemetry Collector sidecar.
Canary / Blue‑Green Deployments Gradual rollout, rollback safety. Argo Rollouts, Spinnaker, Helm --set values.
Infrastructure as Code (IaC) Version control of deployment descriptors. Terraform, Pulumi, Kustomize, Helm Charts.
CI/CD Pipelines Automated build, test, release. GitHub Actions, GitLab CI, Jenkins, Tekton, Skaffold for dev loops.
Feature Flags Toggle functionality at runtime. Togglz, LaunchDarkly, FF4J.

Sample Dockerfile (Spring Boot + JDK 21)


dockerfile
# ---- Build Stage ----
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B
COPY src src
RUN ./mvnw package -DskipTests

# ----
Enter fullscreen mode Exit fullscreen mode

Top comments (0)