Skip to content
Home
Java Microservices: Spring Cloud, Service Discovery, and Resilience

Java Microservices: Spring Cloud, Service Discovery, and Resilience

Java Java 7 min read 1479 words Beginner ExcellentWiki Editorial Team

Microservices architecture decomposes applications into independently deployable services that communicate over the network. Java, with Spring Boot and Spring Cloud, provides the most mature ecosystem for building microservices. This guide covers service decomposition, Spring Cloud components (Eureka, Gateway, Config Server, Circuit Breaker), inter-service communication, observability, and deployment patterns.

Service Decomposition Principles

The fundamental challenge in microservices is defining service boundaries. Sam Newman’s Building Microservices recommends decomposing by business capability — each service owns a specific domain function. The bounded context pattern from Domain-Driven Design (Eric Evans) provides the conceptual framework.

Good service boundaries exhibit:

  • High cohesion: related functionality stays together
  • Loose coupling: services interact through well-defined APIs
  • Data ownership: each service owns its database schema
  • Independent deployability: services can be released independently

Anti-patterns include splitting by layer (separate “data service” and “logic service”) or creating services so fine-grained that orchestration overhead dominates.

Spring Cloud Components

Spring Cloud provides the “distributed systems toolkit” for building microservices on Spring Boot. The Spring Cloud documentation explains each component’s role.

Service Discovery with Eureka

Service discovery enables services to locate each other without hardcoded hosts and ports. Netflix Eureka is a client-side discovery service:

// Application class
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServiceApplication { }

// Configuration
eureka.server.enable-self-preservation: false

Clients register themselves:

@SpringBootApplication
@EnableDiscoveryClient
public class ArticleServiceApplication { }

// Calling another service
@RestController
public class ArticleController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/articles/{id}/author")
    public Author getAuthor(@PathVariable Long id) {
        // Resolved via Eureka — author-service is the application name
        return restTemplate.getForObject("http://author-service/authors/{id}", Author.class, id);
    }
---

Spring Cloud Gateway

The API gateway is the single entry point for all clients. It routes requests, applies security, and can perform rate limiting:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("articles", r -> r.path("/api/articles/**")
            .uri("lb://article-service"))
        .route("authors", r -> r.path("/api/authors/**")
            .uri("lb://author-service"))
        .build();
---

Spring Cloud Gateway is reactive (built on WebFlux and Project Reactor). It outperforms Zuul (the predecessor) and integrates natively with Spring Security OAuth2.

Spring Cloud Config Server

Centralized configuration externalizes properties from service deployments:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication { }

Configuration files (YAML or properties) are stored in a Git repository. Services fetch their config on startup and can refresh at runtime via Spring Cloud Bus (backed by RabbitMQ or Kafka).

Circuit Breakers with Resilience4j

Resilience4j replaces the deprecated Hystrix. It provides circuit breakers, retries, rate limiters, and bulkheads:

@CircuitBreaker(name = "authorService", fallbackMethod = "getDefaultAuthor")
public Author getAuthor(Long id) {
    return restTemplate.getForObject("http://author-service/authors/{id}", Author.class, id);
---

public Author getDefaultAuthor(Long id, Throwable t) {
    return new Author(id, "Unknown", "unknown@excellentwiki.com");
---

Configuration in application.yml:

resilience4j.circuitbreaker:
  instances:
    authorService:
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 30s

Inter-Service Communication

Services communicate via synchronous HTTP/REST or asynchronous messaging.

REST with Spring Cloud OpenFeign declaratively defines HTTP clients:

@FeignClient(name = "author-service")
public interface AuthorClient {
    @GetMapping("/authors/{id}")
    Author getAuthor(@PathVariable Long id);
---

Asynchronous messaging with RabbitMQ or Kafka decouples services. The event-driven approach improves resilience — if a consumer is down, messages are retained until it reconnects.

Distributed Tracing with Micrometer

Tracing across service boundaries requires correlation IDs. Micrometer Tracing (formerly Spring Cloud Sleuth) integrates with OpenTelemetry or Zipkin:

management.tracing.sampling.probability: 1.0

Each service automatically tags logs and spans with traceId and spanId. Zipkin or Jaeger visualizes the trace graph, making latency bottlenecks visible.

Observability: Metrics, Logs, Traces

  • Metrics: Micrometer + Prometheus + Grafana for JVM metrics, request rates, and circuit breaker state.
  • Logs: Structured JSON logging with Logback + ELK (Elasticsearch, Logstash, Kibana) or Loki + Grafana.
  • Traces: Distributed tracing as described above.

The three pillars together provide the observability needed to operate microservices in production.

Deployment Patterns

  • Containerization: Each service runs in a Docker container. Use multi-stage builds for small images.
  • Orchestration: Kubernetes (K8s) is the standard. Deployments, services, config maps, and secrets map directly to microservice concepts.
  • Service Mesh: Istio or Linkerd offload service discovery, traffic management, and mTLS from application code to a sidecar proxy.
  • Serverless: AWS Lambda or Google Cloud Run can host microservices without managing servers, though cold starts remain a concern.

FAQ

Q: When should I avoid microservices? A: For small teams or early-stage products, a monolith is faster to build and evolve. Start monolithic, split when you need independent scaling or deployment velocity.

Q: How do I handle distributed transactions? A: Avoid distributed transactions. Use the Saga pattern — a sequence of local transactions with compensating actions for rollback. Axon Framework or Eventuate Tram supports Saga orchestration.

Q: What is the difference between Eureka and Kubernetes service discovery? A: Eureka is application-level, requiring code changes and a Eureka server. Kubernetes DNS-based discovery (service-name.namespace.svc.cluster.local) is infrastructure-level and transparent to applications.

Q: Which message broker works best with Spring Cloud? A: RabbitMQ is simpler and sufficient for most use cases. Kafka is better for high-throughput, replay, and event sourcing scenarios.

Q: How do I test microservices? A: Use Testcontainers for integration tests, WireMock for stubbing external services, and Spring Cloud Contract for consumer-driven contract tests.

Testing Strategies

Microservices require multi-layered testing. The test pyramid for distributed systems includes:

  • Unit tests: Isolated service logic with mocked dependencies. Use JUnit 5 + Mockito.
  • Integration tests: Real databases via Testcontainers, embedded message brokers, and HTTP clients.
  • Contract tests: Spring Cloud Contract ensures service providers and consumers agree on API semantics.
  • Component tests: Deploy the service in a test container with real dependencies for end-to-end validation.
  • End-to-end tests: Deploy the full system (or representative subset) in a staging environment.

Testcontainers is essential for integration testing. It spins up PostgreSQL, Kafka, and Redis containers alongside tests:

@Testcontainers
class ArticleServiceIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
        .withDatabaseName("excellentwiki");

    @Test
    void shouldSaveAndRetrieveArticle() {
        // postgres.getJdbcUrl() provides the dynamic connection string
    }
---

Observability in Production

Beyond the three pillars, microservices benefit from structured logging correlation. Add traceId and spanId to every log entry via MDC (Mapped Diagnostic Context):

MDC.put("traceId", currentTraceContext().traceId());
log.info("Processing order {}", orderId);
MDC.clear();

Micrometer’s MeterRegistry exposes JVM metrics, HTTP request metrics, and custom business metrics (order count, payment failures). Prometheus scrapes these endpoints, and Grafana dashboards visualize service health, latency distributions, and error rates.

Security in Microservices

Microservices introduce unique security challenges — inter-service communication, decentralized authentication, and perimeter-less trust models.

OAuth2 and JWT: Spring Cloud Security integrates with Authorization Servers (Keycloak, Auth0, Okta). Each service validates JWT tokens locally without calling the auth server:

spring.security.oauth2.resourceserver.jwt.issuer-uri: https://auth.excellentwiki.com

mTLS: Mutual TLS between services ensures both parties verify each other’s certificates. Service mesh implementations (Istio, Linkerd) automate mTLS without application changes.

API key rotation: For service-to-service calls at the gateway level, rotate keys on a 90-day cycle. Store secrets in Kubernetes secrets or HashiCorp Vault — never in configuration files.

Rate limiting: Spring Cloud Gateway supports RequestRateLimiter filter based on Redis or in-memory counters. Per-client rate limits prevent noisy neighbors from degrading service for all tenants.

Database per service is a core microservices principle, but it introduces data consistency challenges. The Saga pattern orchestrates multi-step transactions across services without distributed locks. Implement Sagas with choreography (each service emits events) or orchestration (a coordinator service manages the flow). Axon Framework and Eventuate provide production-grade Saga implementations for the JVM.

Event sourcing — storing state changes as an append-only event log — pairs naturally with microservices and CQRS (Command Query Responsibility Segregation). Axon Framework provides event sourcing infrastructure for the JVM with aggregate modeling, event handlers, and replay support. The tradeoff is operational complexity: event stores require specialized infrastructure and schema evolution must be managed across all event versions.

Health checks, readiness probes, and liveness probes are essential for Kubernetes deployments. Spring Boot Actuator provides /actuator/health with built-in integrations for databases, Redis, Kafka, and other infrastructure. Custom health indicators extend this to application-specific dependencies:

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        if (databaseIsResponsive()) {
            return Health.up().build();
        }
        return Health.down(new Exception("Database unreachable")).build();
    }
---

The reactive stack (Spring WebFlux, Project Reactor) offers an alternative concurrency model for microservices. WebFlux handles backpressure natively and can serve more concurrent connections with fewer threads than the traditional servlet stack. However, reactive programming’s learning curve and debugging complexity have limited its adoption compared to virtual threads, which provide similar scalability benefits with blocking-style code.

The decision to adopt microservices should be driven by organizational structure (Conway’s Law), deployment velocity requirements, and scalability needs — not by technology trends. Teams that align service boundaries with team boundaries, invest in CI/CD and observability from day one, and enforce API contracts through contract testing have the highest success rates with microservices.

Configuration management in microservices must handle environment-specific values securely. Spring Cloud Config Server integrates with Git backends and Vault for secrets. Kubernetes ConfigMaps and Secrets provide native alternatives without additional infrastructure. For dynamic reconfiguration without restart, combine Spring Cloud Bus with RabbitMQ to broadcast property changes to all service instances.

For further reading, consult Building Microservices by Sam Newman and the Spring Cloud Reference Documentation. See also our Java Enterprise Guide for standards-based alternatives to Spring Cloud.

For a comprehensive overview, read our article on Java 17 21 Features.

Section: Java 1479 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top