Spring Cloud in One Shot — The Ultimate Cheatsheet

    Spring Cloud in One Shot — The Ultimate Cheatsheet

    A simple, practical guide to understanding how Spring Cloud helps build real microservices for production. It covers the key pieces like service discovery, API Gateway, configuration management, load balancing, and fault tolerance — all explained in a clear and straightforward way. Perfect for developers who want to move from basic Spring Boot to building scalable, production-ready microservices.

    default profile

    Shreyash Gurav

    March 02, 2026

    26 min read

    Spring Cloud in One Shot — The Ultimate Cheatsheet

    A practical reference for Java backend developers building microservices with Spring Cloud, covering essential concepts such as service discovery, API gateways, centralized configuration, client-side load balancing, resilience patterns, event-driven communication, distributed tracing, and production-ready monitoring and observability.


    1. Microservices Fundamentals#

    Monolith vs Microservices#

    A monolith packages all application features — UI, business logic, data access — into a single deployable unit. Microservices split that unit into independently deployable services, each owning a single business capability.

    AspectMonolithMicroservices
    DeploymentSingle artifactIndependent per service
    ScalingScale entire appScale individual services
    TechnologySingle stackPolyglot allowed
    Failure impactEntire app affectedIsolated to service
    Team structureOne teamAutonomous teams
    Development speedFast initiallyFaster at scale

    Microservices Architecture Principles#

    • Single Responsibility — each service owns exactly one business domain
    • Loose Coupling — services communicate over well-defined APIs; no shared databases
    • High Cohesion — related logic lives together inside the same service
    • Decentralized Data — each service manages its own datastore
    • Design for Failure — assume any downstream call can fail; handle gracefully
    • Automate Everything — CI/CD pipelines, containerization, and infrastructure as code

    Challenges in Distributed Systems#

    Distributed systems introduce problems that simply do not exist in a monolith:

    • Network latency and partial failures
    • Data consistency across service boundaries
    • Service discovery — how does Service A find Service B?
    • Distributed tracing — how do you follow a request across 10 services?
    • Security — each service must authenticate callers
    • Operational complexity — many moving parts to monitor and deploy

    Service-to-Service Communication#

    Synchronous communication — the caller waits for a response.

    • HTTP/REST using RestTemplate or WebClient
    • gRPC for high-performance binary protocol
    • OpenFeign for declarative HTTP clients

    Asynchronous communication — the caller publishes an event and moves on.

    • Apache Kafka for high-throughput event streaming
    • RabbitMQ for message queuing with routing
    • Event sourcing patterns

    When to choose which:

    Use Synchronous WhenUse Asynchronous When
    Immediate response is requiredLoose coupling is priority
    Simple request-responseHigh throughput needed
    Real-time validationServices can tolerate delay
    UI is waiting for resultLong-running processing
    Microservices Architecture Overview

    2. Introduction to Spring Cloud#

    What is Spring Cloud#

    Spring Cloud is a collection of tools and frameworks built on top of Spring Boot that solves the common infrastructure problems in distributed systems. It is not a single library but an ecosystem of coordinated projects.

    Why Spring Cloud#

    Without Spring Cloud, a team building microservices would need to integrate Eureka, Zuul, Ribbon, Hystrix, and dozens of other libraries manually, write glue code, and maintain compatibility. Spring Cloud provides opinionated, pre-integrated solutions for all of these concerns with sensible defaults.

    Spring Cloud Ecosystem#

    ModulePurpose
    Spring Cloud NetflixEureka, Ribbon, Hystrix (largely in maintenance mode)
    Spring Cloud GatewayAPI routing and filtering
    Spring Cloud ConfigCentralized configuration server
    Spring Cloud LoadBalancerClient-side load balancing
    Spring Cloud OpenFeignDeclarative HTTP clients
    Spring Cloud StreamEvent-driven messaging abstraction
    Spring Cloud Sleuth / MicrometerDistributed tracing
    Spring Cloud BusConfiguration broadcast via messaging
    Spring Cloud ContractConsumer-driven contract testing
    Spring Cloud KubernetesNative Kubernetes integration
    Spring Cloud VaultSecrets management with HashiCorp Vault

    Spring Boot and Spring Cloud Relationship#

    Spring Boot provides the application runtime, auto-configuration, and embedded server. Spring Cloud builds on Spring Boot's auto-configuration mechanism to wire distributed system infrastructure automatically. Every Spring Cloud service is a Spring Boot application. Spring Cloud adds the coordination layer.

    <!-- pom.xml — Spring Cloud BOM manages all version compatibility --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

    Always import the Spring Cloud BOM. Never manage Spring Cloud dependency versions individually — version mismatches between modules cause subtle, hard-to-debug failures.


    3. Service Discovery#

    What is Service Discovery#

    In a static deployment you hard-code IP addresses and ports. In a microservices deployment, services are ephemeral — they start, stop, scale up, and scale down dynamically. Service discovery solves the question: given a service name, what network address should I call right now?

    Service Registry#

    The service registry is the central directory. Every service instance registers itself on startup and sends periodic heartbeats. If a heartbeat stops, the registry removes the instance.

    Client-Side vs Server-Side Discovery#

    Client-Side Discovery — the client queries the registry and picks an instance itself. The client holds the load-balancing logic. Spring Cloud uses this model.

    Server-Side Discovery — the client calls a router (load balancer), which queries the registry internally. AWS ALB and Kubernetes Services use this model.

    Netflix Eureka#

    Eureka is the most widely used service registry in the Spring Cloud ecosystem. It is designed for AWS-style availability — it prefers availability over consistency (AP in CAP theorem). When the registry cannot reach enough peers, it enters self-preservation mode and stops evicting instances.

    Eureka Server Setup#

    <!-- dependency --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
    @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
    # application.yml — standalone Eureka server server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false # this server does not register itself fetch-registry: false # this server does not fetch from another registry

    Eureka Client Setup#

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
    @SpringBootApplication @EnableDiscoveryClient // optional from Spring Cloud 2021+, auto-detected public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
    # application.yml — Eureka client spring: application: name: order-service # this becomes the service name in the registry eureka: client: service-url: defaultZone: <http://localhost:8761/eureka/> instance: prefer-ip-address: true lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 90

    Service Registration and Heartbeats#

    • On startup, the client POSTs its metadata (hostname, IP, port, health URL, status) to the registry
    • Every 30 seconds (default), the client sends a heartbeat PUT request
    • If the registry receives no heartbeat for 90 seconds, it marks the instance as DOWN
    • If the registry loses over 85% of expected heartbeats from all clients, it enters self-preservation mode and stops evicting any instance — this prevents mass deregistration during network partitions

    4. API Gateway#

    API Gateway Pattern#

    An API Gateway is the single entry point for all client requests. Instead of clients calling each microservice directly, they call the gateway. The gateway handles routing, authentication, rate limiting, SSL termination, request transformation, and logging in one place.

    Benefits:

    • Clients are decoupled from internal service topology
    • Cross-cutting concerns are handled once, not in every service
    • Service URLs can change without affecting clients

    Spring Cloud Gateway#

    Spring Cloud Gateway is the modern, reactive replacement for Netflix Zuul. It is built on Project Reactor and Spring WebFlux, which means it is non-blocking and handles high concurrency efficiently.

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>

    Routing#

    A route is the fundamental unit in Spring Cloud Gateway. Each route has:

    • An ID for identification
    • A URI — the downstream service address
    • Predicates — conditions that must match for the route to apply
    • Filters — transformations applied to request or response
    spring: cloud: gateway: routes: - id: order-service-route uri: lb://order-service # lb:// triggers load-balanced discovery predicates: - Path=/api/orders/** filters: - StripPrefix=1 # removes /api before forwarding

    Predicates#

    Predicates determine whether a route matches an incoming request. Multiple predicates on one route are ANDed together.

    PredicateExampleDescription
    PathPath=/api/users/**Matches URL path pattern
    MethodMethod=GET,POSTMatches HTTP method
    HeaderHeader=X-Request-Id, \\\\d+Matches header with regex
    QueryQuery=color, redMatches query parameter
    HostHost=**.example.comMatches Host header
    AfterAfter=2024-01-01T00:00:00ZMatches requests after datetime
    WeightWeight=group1, 80Weighted routing for canary deploys

    Filters#

    Filters transform requests or responses. They run in order and fall into two categories: GatewayFilter (per route) and GlobalFilter (all routes).

    filters: - AddRequestHeader=X-Request-Source, gateway - AddResponseHeader=X-Response-Time, 200ms - StripPrefix=2 - RewritePath=/api/(?<segment>.*), /$\\\\{segment} - CircuitBreaker=name=orderCircuit,fallbackUri=/fallback - RequestRateLimiter=redis-rate-limiter - Retry=retries:3,statuses:BAD_GATEWAY

    Custom filter with Java:

    @Component public class LoggingGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); System.out.println("Incoming request: " + path); return chain.filter(exchange) .then(Mono.fromRunnable(() -> System.out.println("Response status: " + exchange.getResponse().getStatusCode()))); } @Override public int getOrder() { return -1; } }

    Authentication at Gateway#

    Centralize JWT validation at the gateway so individual services do not need to repeat it.

    @Component public class JwtAuthenticationFilter implements GlobalFilter { @Value("${jwt.secret}") private String secret; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest() .getHeaders() .getFirst(HttpHeaders.AUTHORIZATION); if (token == null || !token.startsWith("Bearer ")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // validate token, then pass claims downstream as header exchange.getRequest().mutate() .header("X-User-Id", extractUserId(token)); return chain.filter(exchange); } }

    Rate Limiting#

    Spring Cloud Gateway integrates with Redis for distributed rate limiting using the token bucket algorithm.

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
    spring: cloud: gateway: routes: - id: rate-limited-route uri: lb://product-service predicates: - Path=/api/products/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 # tokens added per second redis-rate-limiter.burstCapacity: 20 # max tokens in bucket redis-rate-limiter.requestedTokens: 1 # tokens per request
    API Gateway Routing Flow

    5. Centralized Configuration#

    Externalized Configuration#

    The Twelve-Factor App methodology mandates that configuration is separated from code. In microservices, this means you do not bake environment-specific properties into your JAR. You fetch them at runtime from a central source.

    Benefits:

    • One change propagates to all service instances without redeployment
    • Environment-specific config (dev, staging, prod) managed in one place
    • Secrets managed outside source code
    • Audit trail via Git history

    Spring Cloud Config Server#

    The Config Server is a standalone Spring Boot application that serves configuration over HTTP. It reads from a Git repository, local file system, HashiCorp Vault, or AWS S3.

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
    @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
    # application.yml — Config Server pointing to Git server: port: 8888 spring: cloud: config: server: git: uri: <https://github.com/yourorg/config-repo> default-label: main search-paths: '{application}' # subfolder per service clone-on-start: true

    Config Server URL pattern:

    /{application}/{profile}[/{label}] # Examples: GET <http://localhost:8888/order-service/prod> GET <http://localhost:8888/order-service/dev/main>

    Config Client#

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
    # application.yml — microservice pointing to Config Server spring: application: name: order-service # maps to file name in config repo config: import: "configserver:<http://localhost:8888>" profiles: active: dev

    File resolution in the Git repo follows this priority order (highest wins):

    1. order-service-dev.yml
    2. order-service.yml
    3. application-dev.yml
    4. application.yml

    Git-based Configuration Management#

    Organize the Git repository cleanly:

    config-repo/ ├── application.yml # shared across all services ├── application-prod.yml # shared prod overrides ├── order-service/ │ ├── order-service.yml │ ├── order-service-dev.yml │ └── order-service-prod.yml ├── user-service/ │ └── user-service.yml

    Refresh Scope#

    Without @RefreshScope, a bean reads its configuration once at startup. With @RefreshScope, the bean is re-initialized when a /actuator/refresh endpoint is called.

    @RestController @RefreshScope // re-inject config on refresh public class FeatureFlagController { @Value("${feature.new-checkout-enabled:false}") private boolean newCheckoutEnabled; @GetMapping("/feature-status") public String status() { return "New checkout: " + newCheckoutEnabled; } }

    Trigger a refresh on one instance:

    curl -X POST <http://localhost:8080/actuator/refresh>

    Dynamic Configuration Reload with Spring Cloud Bus#

    To refresh all instances simultaneously without calling each one individually, use Spring Cloud Bus. It broadcasts the refresh event over a message broker (Kafka or RabbitMQ) to every subscribed instance.

    <!-- Add to Config Server and all clients --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-kafka</artifactId> </dependency>
    # Refresh all instances in one call curl -X POST <http://localhost:8888/actuator/busrefresh>

    6. Client-Side Load Balancing#

    Load Balancing Concepts#

    Load balancing distributes incoming requests across multiple instances of a service to avoid overloading any single instance. In the Spring Cloud model, the load balancer runs on the client side — meaning the service making the call decides which instance to call, based on a list it receives from the service registry.

    Spring Cloud LoadBalancer#

    Spring Cloud LoadBalancer replaced Netflix Ribbon as the default client-side load balancer starting from Spring Cloud 2020. It is included automatically when you add the Eureka client dependency.

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>

    Using @LoadBalanced with RestTemplate#

    @Configuration public class RestTemplateConfig { @Bean @LoadBalanced // wraps RestTemplate to resolve service names via registry public RestTemplate restTemplate() { return new RestTemplate(); } } @Service public class OrderService { @Autowired private RestTemplate restTemplate; public Product getProduct(Long id) { // uses service name, not hardcoded URL return restTemplate.getForObject( "<http://product-service/api/products/>" + id, Product.class ); } }

    Using @LoadBalanced with WebClient (Reactive)#

    @Configuration public class WebClientConfig { @Bean @LoadBalanced public WebClient.Builder webClientBuilder() { return WebClient.builder(); } } @Service public class ReactiveOrderService { @Autowired private WebClient.Builder webClientBuilder; public Mono<Product> getProduct(Long id) { return webClientBuilder.build() .get() .uri("<http://product-service/api/products/>" + id) .retrieve() .bodyToMono(Product.class); } }

    Load Balancing Algorithms#

    AlgorithmBehaviorWhen to Use
    Round Robin (default)Cycles through instances in orderMost scenarios; even distribution
    RandomPicks a random instanceReduces hot spots under bursty load
    CustomImplement ReactorServiceInstanceLoadBalancerCustom business logic needed

    Custom Load Balancer Configuration#

    public class CustomLoadBalancerConfig { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer( Environment env, LoadBalancerClientFactory loadBalancerClientFactory) { String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer( loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name ); } } // Apply to a specific client @LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfig.class) public class ApplicationConfig { }

    Integration with Service Discovery#

    The ServiceInstanceListSupplier fetches the instance list from Eureka (or Consul or Kubernetes). The load balancer then applies its algorithm to that list. Caching is enabled by default with a 35-second TTL to avoid hammering the registry on every request.

    spring: cloud: loadbalancer: ribbon: enabled: false # ensure Ribbon is not on classpath cache: ttl: 35s capacity: 256

    7. Fault Tolerance and Resilience#

    Distributed System Failures#

    In a distributed system with 10 services, if each has 99.9% uptime, the end-to-end availability of a chain through all 10 is approximately 99%. Without resilience patterns, a slow or failing downstream service can cascade into a full system outage through thread pool exhaustion.

    Circuit Breaker Pattern#

    The circuit breaker sits around an external call. It monitors failures. When the failure rate exceeds a threshold, it opens and stops all calls to that service for a cooldown period. After the cooldown, it enters half-open state and allows a limited number of test calls through.

    States:

    • CLOSED — normal operation; calls pass through
    • OPEN — calls fail immediately without hitting the downstream service
    • HALF-OPEN — a limited number of probe calls are allowed to test recovery

    Resilience4j#

    Resilience4j is the recommended library for Spring Cloud fault tolerance. It provides circuit breaker, retry, rate limiter, bulkhead, and time limiter as individual composable decorators.

    <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

    Circuit Breaker Configuration#

    resilience4j: circuitbreaker: instances: product-service: sliding-window-type: COUNT_BASED sliding-window-size: 10 # evaluate last 10 calls failure-rate-threshold: 50 # open if 50%+ fail wait-duration-in-open-state: 10s # stay open for 10 seconds permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true
    @Service public class ProductService { @CircuitBreaker(name = "product-service", fallbackMethod = "getProductFallback") public Product getProduct(Long id) { return restTemplate.getForObject("<http://product-service/api/products/>" + id, Product.class); } public Product getProductFallback(Long id, Exception ex) { return new Product(id, "Unavailable", 0.0); // default response } }

    Retry Pattern#

    resilience4j: retry: instances: product-service: max-attempts: 3 wait-duration: 500ms retry-exceptions: - java.io.IOException - java.util.concurrent.TimeoutException ignore-exceptions: - com.example.ProductNotFoundException
    @Retry(name = "product-service", fallbackMethod = "getProductFallback") public Product getProduct(Long id) { return restTemplate.getForObject("...", Product.class); }

    Bulkhead Pattern#

    A bulkhead limits the number of concurrent calls to a service, preventing one slow service from consuming all available threads.

    resilience4j: bulkhead: instances: product-service: max-concurrent-calls: 10 max-wait-duration: 100ms
    @Bulkhead(name = "product-service", type = Bulkhead.Type.SEMAPHORE) public Product getProduct(Long id) { return restTemplate.getForObject("...", Product.class); }

    Rate Limiter#

    resilience4j: ratelimiter: instances: product-service: limit-for-period: 100 # max 100 calls limit-refresh-period: 1s # per second timeout-duration: 25ms # wait up to 25ms for a slot
    @RateLimiter(name = "product-service") public Product getProduct(Long id) { return restTemplate.getForObject("...", Product.class); }

    Timeout Handling#

    resilience4j: timelimiter: instances: product-service: timeout-duration: 2s cancel-running-future: true

    Combining Multiple Resilience Patterns#

    Annotations can be stacked. The execution order is: TimeLimiter -> CircuitBreaker -> RateLimiter -> Bulkhead -> Retry.

    @CircuitBreaker(name = "product-service", fallbackMethod = "fallback") @Retry(name = "product-service") @Bulkhead(name = "product-service") @TimeLimiter(name = "product-service") public CompletableFuture<Product> getProduct(Long id) { return CompletableFuture.supplyAsync(() -> restTemplate.getForObject("...", Product.class)); }
    Circuit Breaker State Machine

    8. Distributed Tracing#

    Observability in Microservices#

    A single user request in a microservices system touches multiple services. When something goes wrong or is slow, you need to reconstruct the complete journey of that request across service boundaries. Distributed tracing provides this capability.

    Trace ID and Span ID#

    • Trace ID — a single unique identifier assigned when a request first enters your system. It follows the request through every service it touches.
    • Span ID — a unique identifier for one unit of work within a trace. Each service call creates a new span. Spans record their parent span ID to form a tree.
    • Parent Span ID — links a child span to the span that initiated it.
    Trace: a1b2c3d4 ├── Span: gateway (e5f6) 0ms - 120ms │ ├── Span: order-service (a7b8) 5ms - 115ms │ │ ├── Span: product-service (c9d0) 10ms - 60ms │ │ └── Span: inventory-service (e1f2) 65ms - 110ms │ └── Span: auth-service (a3b4) 6ms - 20ms

    Micrometer Tracing with Zipkin#

    From Spring Boot 3, Spring Cloud Sleuth is replaced by Micrometer Tracing. The concepts are identical; the library changed.

    <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 # sample 100% in dev; use 0.1 in prod zipkin: tracing: endpoint: <http://localhost:9411/api/v2/spans>

    Micrometer Tracing auto-instruments:

    • RestTemplate
    • WebClient
    • Feign clients
    • Kafka producers and consumers
    • @Scheduled methods
    • @Async methods

    The trace ID and span ID are automatically injected into the MDC (Mapped Diagnostic Context), so they appear in every log line without extra code:

    2024-01-15 10:23:45 [order-service,,a1b2c3d4e5f6,e5f6a7b8] INFO OrderController - Processing order 1234

    Log format: [service-name, trace-id, span-id]

    Running Zipkin Locally#

    docker run -d -p 9411:9411 openzipkin/zipkin

    Access the UI at http://localhost:9411. You can search by trace ID, service name, and time range.


    9. Event-Driven Microservices#

    Event-Driven Architecture#

    In event-driven architecture, services communicate by publishing and consuming events through a message broker. The producer has no knowledge of consumers. This creates strong decoupling and allows services to evolve independently.

    Spring Cloud Stream#

    Spring Cloud Stream is a framework for building event-driven microservices connected to message brokers. It abstracts the broker-specific APIs behind a uniform programming model using Java functional interfaces.

    <!-- For Kafka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-kafka</artifactId> </dependency> <!-- For RabbitMQ --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>

    Producers and Consumers#

    Spring Cloud Stream uses three functional interfaces:

    InterfaceRoleBindings Created
    Supplier<T>Source / Produceroutput
    Consumer<T>Sink / Consumerinput
    Function<T,R>Processorinput + output
    // Producer — publishes OrderCreatedEvent every second (Supplier auto-triggered) @Bean public Supplier<OrderCreatedEvent> orderCreatedSupplier() { return () -> new OrderCreatedEvent(UUID.randomUUID(), "PENDING"); } // Consumer — processes incoming events @Bean public Consumer<OrderCreatedEvent> processOrder() { return event -> { System.out.println("Processing order: " + event.getOrderId()); inventoryService.reserve(event); }; } // Processor — receives, transforms, and re-publishes @Bean public Function<OrderCreatedEvent, OrderProcessedEvent> processAndPublish() { return event -> { inventoryService.reserve(event); return new OrderProcessedEvent(event.getOrderId(), "RESERVED"); }; }

    Binding Configuration#

    spring: cloud: stream: bindings: # naming convention: functionName + -in-0 (input) or -out-0 (output) processOrder-in-0: destination: orders-topic group: inventory-service # consumer group for competing consumers orderCreatedSupplier-out-0: destination: orders-topic kafka: bindings: processOrder-in-0: consumer: startOffset: earliest

    Apache Kafka#

    Kafka is a distributed, partitioned, replicated commit log. It is optimized for high-throughput, fault-tolerant event streaming.

    Key concepts:

    ConceptDescription
    TopicNamed category for messages
    PartitionTopic split for parallelism; messages in a partition are ordered
    OffsetPosition of a message within a partition
    ProducerWrites messages to topics
    ConsumerReads messages from topics
    Consumer GroupSet of consumers that cooperate; each partition assigned to one consumer
    BrokerSingle Kafka server node
    # Start Kafka with Docker Compose version: '3' services: zookeeper: image: confluentinc/cp-zookeeper:7.5.0 environment: ZOOKEEPER_CLIENT_PORT: 2181 kafka: image: confluentinc/cp-kafka:7.5.0 ports: - "9092:9092" environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

    RabbitMQ#

    RabbitMQ uses exchanges and queues. Producers publish to exchanges; exchanges route to queues based on binding rules.

    Exchange TypeRouting Behavior
    DirectRoutes to queue whose binding key matches the message routing key exactly
    TopicMatches routing key patterns with * and # wildcards
    FanoutBroadcasts to all bound queues; ignores routing key
    HeadersRoutes based on message header attributes
    Event-Driven Microservices Architecture

    10. Security in Microservices#

    Microservices Security Challenges#

    • Each service needs to know who is calling it and whether they are authorized
    • Tokens need to propagate through service chains
    • Services must not blindly trust data from other internal services without validation
    • Secrets (DB passwords, API keys) cannot be stored in config files or source code

    Spring Security with OAuth2#

    OAuth2 is the industry-standard protocol for authorization. In a microservices context, a client authenticates with an Authorization Server (Keycloak, Okta, Auth0) and receives a JWT access token. It presents this token on every API call. Services validate the token without calling the Authorization Server on every request.

    JWT Authentication#

    A JWT (JSON Web Token) has three Base64URL-encoded parts: header, payload, and signature.

    eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImV4cCI6MTcwNTM2NTIwMH0.<signature>

    Payload example:

    { "sub": "user123", "roles": ["ROLE_USER"], "exp": 1705365200, "iss": "<http://auth.example.com>" }

    Resource Server Configuration (Spring Boot 3)#

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
    @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) ) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ); return http.build(); } private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter(); converter.setAuthoritiesClaimName("roles"); converter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(converter); return jwtConverter; } }
    spring: security: oauth2: resourceserver: jwt: issuer-uri: <http://localhost:8080/realms/myrealm> # Keycloak issuer

    Token Propagation#

    When Service A calls Service B, it must forward the JWT. Use the TokenRelay filter in the gateway, or propagate manually with Feign.

    # Gateway: forward token to downstream services automatically spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** filters: - TokenRelay= # forwards Authorization header downstream

    For Feign clients:

    @Configuration public class FeignSecurityConfig { @Bean public RequestInterceptor requestInterceptor() { return template -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { String token = attributes.getRequest() .getHeader(HttpHeaders.AUTHORIZATION); if (token != null) { template.header(HttpHeaders.AUTHORIZATION, token); } } }; } }

    11. Distributed Transactions#

    Transaction Problems in Microservices#

    The classic database transaction (ACID) does not work across services that own separate databases. If an order service writes to its database and then calls inventory service, and inventory fails, you cannot simply roll back the order database — the transaction boundary is gone.

    Eventual Consistency#

    Accept that in a distributed system, data will not always be instantly consistent across all services. Design flows so that all services will eventually reach a consistent state, even if there are brief windows of inconsistency.

    Saga Pattern#

    A saga is a sequence of local transactions. Each service in the chain performs its local transaction and publishes an event (or calls the next service). If any step fails, compensating transactions undo the previous steps.

    Choreography Saga#

    Each service listens for events and decides what to do next independently. There is no central coordinator.

    OrderService InventoryService PaymentService | | | Create Order | | Publish: OrderCreated | | | ---------> Reserve Stock | | Publish: StockReserved | | | -----------> Charge Payment | | Publish: PaymentCompleted Confirm Order | |

    Compensating flow on failure:

    PaymentService fails Publish: PaymentFailed | InventoryService listens → Release Stock | OrderService listens → Cancel Order

    Orchestration Saga#

    A central orchestrator tells each service what to do. It tracks the saga state and issues compensating commands when a failure occurs.

    @SagaOrchestrationStart public void placeOrder(PlaceOrderCommand command) { orderService.createOrder(command); sagaManager.startSaga(command.getOrderId()); } // Using Axon Framework Saga example @Saga public class OrderSaga { @SagaEventHandler(associationProperty = "orderId") public void on(OrderCreatedEvent event) { commandGateway.send(new ReserveStockCommand(event.getOrderId(), event.getProductId())); } @SagaEventHandler(associationProperty = "orderId") public void on(StockReservationFailedEvent event) { commandGateway.send(new CancelOrderCommand(event.getOrderId())); } @EndSaga @SagaEventHandler(associationProperty = "orderId") public void on(PaymentCompletedEvent event) { // saga complete } }
     ChoreographyOrchestration
    CoordinationDecentralizedCentralized orchestrator
    CouplingLooseTighter to orchestrator
    VisibilityHard to traceEasy to trace
    Failure handlingComplex — each service must listen for failuresSimpler — orchestrator handles rollback
    Best forSimple flows with few stepsComplex flows with many conditions

    12. Monitoring and Observability#

    Spring Boot Actuator#

    Actuator exposes operational endpoints over HTTP (or JMX). It is the foundation for all monitoring in a Spring Boot microservice.

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
    management: endpoints: web: exposure: include: health, info, metrics, prometheus, env, loggers, threaddump endpoint: health: show-details: always show-components: always

    Key Actuator endpoints:

    EndpointDescription
    /actuator/healthService health status including custom indicators
    /actuator/metricsAll Micrometer metrics
    /actuator/prometheusPrometheus-formatted metrics
    /actuator/envAll environment properties (sensitive — secure this)
    /actuator/loggersView and change log levels at runtime
    /actuator/threaddumpCurrent thread state
    /actuator/infoApplication metadata
    /actuator/refreshTrigger config refresh (with Cloud Config)

    Custom Health Indicator#

    @Component public class DatabaseHealthIndicator implements HealthIndicator { @Autowired private DataSource dataSource; @Override public Health health() { try (Connection conn = dataSource.getConnection()) { return Health.up() .withDetail("database", "PostgreSQL") .withDetail("status", "connection ok") .build(); } catch (Exception e) { return Health.down() .withDetail("error", e.getMessage()) .build(); } } }

    Micrometer and Prometheus#

    Micrometer is the metrics facade for Spring Boot applications — analogous to SLF4J but for metrics. It supports counters, gauges, timers, and distribution summaries.

    @Service public class OrderService { private final Counter orderCounter; private final Timer orderProcessingTimer; public OrderService(MeterRegistry registry) { this.orderCounter = Counter.builder("orders.created") .description("Total orders created") .tag("region", "us-east") .register(registry); this.orderProcessingTimer = Timer.builder("orders.processing.time") .description("Order processing duration") .register(registry); } public Order createOrder(CreateOrderRequest request) { return orderProcessingTimer.record(() -> { Order order = processOrder(request); orderCounter.increment(); return order; }); } }

    Prometheus scrapes the /actuator/prometheus endpoint. Configure the scrape job:

    # prometheus.yml scrape_configs: - job_name: 'spring-cloud-services' metrics_path: '/actuator/prometheus' scrape_interval: 15s static_configs: - targets: - 'order-service:8080' - 'product-service:8081' - 'user-service:8082'

    Grafana#

    Grafana connects to Prometheus as a data source and provides dashboards. The Spring Boot community maintains pre-built Grafana dashboards for JVM metrics, HTTP request rates, error rates, and circuit breaker states. Import dashboard ID 4701 (JVM Micrometer) from grafana.com as a starting point.

    Logging in Microservices#

    Structured logging (JSON format) is essential in microservices because it allows log aggregation systems (ELK Stack, Grafana Loki) to parse, index, and query logs across all services.

    <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.4</version> </dependency>
    <!-- logback-spring.xml --> <configuration> <springProfile name="prod"> <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <includeMdcKeyName>traceId</includeMdcKeyName> <includeMdcKeyName>spanId</includeMdcKeyName> </encoder> </appender> <root level="INFO"> <appender-ref ref="JSON"/> </root> </springProfile> </configuration>

    Output:

    { "@timestamp":"2024-01-15T10:23:45Z", "level":"INFO","service":"order-service", "traceId":"a1b2c3d4e5f6", "spanId":"e5f6a7b8", "message":"Order 1234 created" }
    Monitoring and Observability Stack

    13. End-to-End Microservices Request Flow#

    Complete Request Trace: Place an Order#

    This section traces one user request — placing an order — through every layer of a production Spring Cloud system.

    End-to-End Microservices Request Flow

    Step-by-step:

    1. Client sends request
      • POST <https://api.example.com/api/orders>
      • Bearer JWT token in Authorization header
    2. API Gateway receives request
      • JWT filter validates token signature against JWKS endpoint
      • Rate limiter checks Redis for remaining token budget
      • Route predicate Path=/api/orders/** matches
    3. Gateway performs service discovery
      • Resolves lb://order-service by querying Eureka registry
      • Receives list of available instances: [192.168.1.10:8080, 192.168.1.11:8080]
    4. Load balancer selects instance
      • Round-robin selects 192.168.1.10:8080
      • TokenRelay filter copies JWT to forwarded request
    5. Circuit breaker check
      • Resilience4j checks state of order-service circuit breaker
      • Circuit is CLOSED — request proceeds
    6. Order Service processes request
      • Validates request body
      • Calls Product Service via Feign client to verify product exists
      • Feign call is load-balanced and has its own circuit breaker
      • Saves order to database with status PENDING
      • Returns 202 Accepted with order ID
    7. Asynchronous event published
      • Order Service publishes OrderCreatedEvent to Kafka topic orders
      • Event contains order ID, product ID, quantity, user ID
    8. Downstream consumers react
      • Inventory Service consumes event, decrements stock
      • Notification Service consumes event, sends confirmation email
      • Payment Service consumes event, initiates charge
      • Each service publishes its own result events
    9. Distributed trace recorded
      • All services attached the same trace ID a1b2c3d4 to their spans
      • Zipkin received all spans and assembled the complete trace tree
      • Total request time visible in Zipkin UI

    Gateway Routing Flow (Condensed)#

    Client Gateway (authenticate, rate limit) Eureka (resolve order-service instances) LoadBalancer (pick instance) CircuitBreaker (check state) → order-service:8080 (process) Feign → product-service (validate) Database (persist) Kafka (publish event) 202 Accepted ← forward response 202 Accepted with order ID

    14. Spring Cloud Project Structure#

    A Spring Cloud system is composed of several Spring Boot applications. The cleanest approach is a Maven or Gradle multi-module project where each module is an independent microservice.

    microservices-project/ ├── pom.xml # parent POM with Spring Cloud BOM ├── config-server/ # Spring Cloud Config Server │ ├── src/main/java/ │ └── src/main/resources/ │ └── application.yml ├── eureka-server/ # Service Registry │ ├── src/main/java/ │ └── src/main/resources/ │ └── application.yml ├── api-gateway/ # Spring Cloud Gateway │ ├── src/main/java/ │ └── src/main/resources/ │ └── application.yml ├── order-service/ # Business microservice │ ├── src/main/java/ │ │ └── com/example/order/ │ │ ├── controller/ │ │ ├── service/ │ │ ├── repository/ │ │ ├── model/ │ │ └── client/ # Feign clients to other services │ └── src/main/resources/ │ └── application.yml ├── product-service/ ├── user-service/ ├── notification-service/ ├── shared-library/ # Common DTOs, exceptions, utils │ └── src/main/java/ │ └── com/example/shared/ │ ├── dto/ │ ├── exception/ │ └── config/ └── config-repo/ # Git repo for Config Server (or separate repo) ├── application.yml ├── order-service.yml ├── order-service-dev.yml └── order-service-prod.yml

    Parent POM#

    <!-- parent pom.xml --> <project> <groupId>com.example</groupId> <artifactId>microservices-project</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> </parent> <properties> <java.version>21</java.version> <spring-cloud.version>2023.0.1</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <modules> <module>config-server</module> <module>eureka-server</module> <module>api-gateway</module> <module>order-service</module> <module>product-service</module> <module>shared-library</module> </modules> </project>

    Config Server — Key Files#

    // ConfigServerApplication.java @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
    # application.yml server: port: 8888 spring: cloud: config: server: git: uri: ${CONFIG_REPO_URI:<https://github.com/yourorg/config-repo>} default-label: main

    Eureka Server — Key Files#

    // EurekaServerApplication.java @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
    # application.yml server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false

    API Gateway — Key Files#

    # application.yml server: port: 8080 spring: application: name: api-gateway cloud: gateway: discovery: locator: enabled: true # auto-create routes from Eureka registry lower-case-service-id: true routes: - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** - id: product-service uri: lb://product-service predicates: - Path=/api/products/** eureka: client: service-url: defaultZone: <http://localhost:8761/eureka/>

    Microservice — Standard Dependencies#

    <!-- dependencies for a typical business microservice --> <dependencies> <!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Config Client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!-- Service Discovery --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Feign HTTP client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- Resilience4j --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> </dependency> <!-- 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> <!-- Actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- Database --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies>

    Shared Library#

    The shared library avoids duplicating DTO classes, exception handling, and common configuration across services.

    shared-library/src/main/java/com/example/shared/ ├── dto/ │ ├── ApiResponse.java # standard API response wrapper │ ├── ErrorResponse.java # error response structure │ └── PagedResponse.java ├── exception/ │ ├── ServiceException.java │ ├── ResourceNotFoundException.java │ └── GlobalExceptionHandler.java └── config/ └── CommonSecurityConfig.java # shared security base configuration

    Startup Order#

    Infrastructure services must start before business services can register and fetch configuration.

    1. Config Server (port 8888) — must start first 2. Eureka Server (port 8761) — registers after Config Server 3. API Gateway (port 8080) — registers after Eureka 4. Business microservices — start last; fetch config, register with Eureka

    Use Docker Compose depends_on with health checks to enforce this ordering in a containerized environment.

    # docker-compose.yml (partial) services: config-server: build: ./config-server ports: ["8888:8888"] healthcheck: test: ["CMD", "curl", "-f", "<http://localhost:8888/actuator/health>"] interval: 10s timeout: 5s retries: 5 eureka-server: build: ./eureka-server ports: ["8761:8761"] depends_on: config-server: condition: service_healthy order-service: build: ./order-service depends_on: eureka-server: condition: service_healthy config-server: condition: service_healthy
    Spring Cloud Service Startup Order

    Quick Reference Card#

    Annotations Summary#

    AnnotationLocationPurpose
    @EnableEurekaServerConfig classStart Eureka registry
    @EnableDiscoveryClientMain classRegister with discovery
    @EnableConfigServerConfig classStart Config Server
    @EnableFeignClientsConfig classActivate Feign scanning
    @RefreshScopeBeanRe-inject on config refresh
    @LoadBalancedBean methodEnable client-side LB
    @CircuitBreakerMethodApply circuit breaker
    @RetryMethodApply retry logic
    @BulkheadMethodApply bulkhead concurrency limit
    @RateLimiterMethodApply rate limiter
    @TimeLimiterMethodApply timeout
    @FeignClientInterfaceDeclare declarative HTTP client

    Default Ports Convention#

    ServiceDefault Port
    Config Server8888
    Eureka Server8761
    API Gateway8080
    Zipkin9411
    Prometheus9090
    Grafana3000
    Kafka9092
    RabbitMQ5672 (AMQP), 15672 (management UI)

    Spring Cloud Version Compatibility#

    Spring BootSpring Cloud
    3.2.x2023.0.x (Leyton)
    3.1.x2022.0.x (Kilburn)
    3.0.x2022.0.x (Kilburn)
    2.7.x2021.0.x (Jubilee)

    Always check the Spring Cloud release train page for the exact compatible versions before starting a new project.


    Conclusion#

    Spring Cloud simplifies building scalable and resilient microservices by providing tools for service discovery, API gateways, configuration management, fault tolerance, tracing, and messaging. This cheatsheet aimed to give a quick overview of the key components and how they work together in a real microservices architecture.

    If you found this cheatsheet helpful, feel free to share it with your friends or fellow developers who are learning Spring Cloud.

    Want to Master Spring Boot and Land Your Dream Job?

    Struggling with coding interviews? Learn Data Structures & Algorithms (DSA) with our expert-led course. Build strong problem-solving skills, write optimized code, and crack top tech interviews with ease

    Learn more
    Spring Boot
    Spring Cloud
    Microservices
    Was it helpful?

    Subscribe to our newsletter

    Read articles from Coding Shuttle directly inside your inbox. Subscribe to the newsletter, and don't miss out.

    More articles