Home / Spring Boot / Messaging and Microservices / Microservices with Spring Cloud

Microservices with Spring Cloud

Microservices split one big application into small services. This creates new problems: how do services find each other, where does configuration live, how do requests enter the system, and how do you trace one request across services. Spring Cloud provides answers, and Kubernetes provides many of them too.

The building blocks

                        ┌───────────── Config Server (Git) ─────────────┐
                        │  central configuration for all services      │
 Client ──► API Gateway ┼─► Order Service ──► Payment Service          │
 (mobile/web)  (routing,│        │    ▲            │                    │
            auth, rate  │        ▼    │ lookup     ▼                    │
            limit)      │   Service Registry (Eureka / Consul / Kubernetes DNS)
                        │        every service registers itself and finds others by NAME
                        └─ Distributed tracing (trace id) + central logs + metrics
ProblemSpring Cloud toolKubernetes alternative
Service discoveryEureka, Consul (client side load balancing)Kubernetes Service and DNS
Central configurationSpring Cloud Config ServerConfigMap and Secret
Single entry pointSpring Cloud GatewayIngress or Gateway API, service mesh
ResilienceResilience4j circuit breakersService mesh (Istio, Linkerd) policies
TracingMicrometer Tracing + OpenTelemetrySame
Inter-service callsRestClient / HTTP interface with @LoadBalanced, OpenFeignPlain HTTP to the service name

Version rule: Spring Cloud releases (for example 2025.0.x) match specific Spring Boot lines. Always take the release train version from the official compatibility table for your Spring Boot version, and import the Spring Cloud BOM.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>   <!-- choose from the compatibility table -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

1. Config Server

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication { public static void main(String[] a) { SpringApplication.run(ConfigServerApplication.class, a); } }
# config-server application.yml
server.port: 8888
spring.cloud.config.server.git:
  uri: https://github.com/mycompany/config-repo
  default-label: main
# the repo holds: order-service.yml, order-service-prod.yml, application.yml (shared) ...
# in each client service
spring:
  application.name: order-service
  config.import: optional:configserver:http://localhost:8888
  profiles.active: prod

The client loads order-service-prod.yml from Git at startup. To change configuration without a restart, use Spring Cloud Bus or Kubernetes rolling restarts, and mark beans with @RefreshScope.

2. Service discovery with Eureka

@SpringBootApplication
@EnableEurekaServer
public class RegistryApplication { public static void main(String[] a) { SpringApplication.run(RegistryApplication.class, a); } }
# each service (client)
eureka:
  client:
    service-url.defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
@Configuration
public class ClientConfig {
    @Bean
    @LoadBalanced                                     // "http://payment-service" is resolved by name, with client side load balancing
    RestClient.Builder loadBalancedBuilder() { return RestClient.builder(); }
}

@Service
public class PaymentGateway {
    private final RestClient client;
    public PaymentGateway(@LoadBalanced RestClient.Builder builder) { this.client = builder.baseUrl("http://payment-service").build(); }

    public String status(String id) { return client.get().uri("/payments/{id}", id).retrieve().body(String.class); }
}

3. API Gateway

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service                    # lb:// = load balanced by service name
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
        - id: payment-service
          uri: lb://payment-service
          predicates:
            - Path=/api/payments/**

The gateway is a natural place for authentication (validate the JWT once), CORS, rate limiting, request logging and adding a correlation id. It is built on WebFlux, so never put blocking code in gateway filters.

Patterns you must know

PatternIdea
Database per serviceEach service owns its data. No shared tables. Other services use the API or events
SagaA business transaction across services is a sequence of local transactions with compensating actions if a step fails (choreography with events, or orchestration by a coordinator)
Transactional OutboxWrite the event to an outbox table in the same DB transaction, then publish it reliably
API CompositionA gateway or a BFF (backend for frontend) calls several services and merges results
CQRSSeparate models for writing and reading, often with events
Idempotent consumersHandle duplicate messages safely
Strangler FigMove a monolith to microservices piece by piece

Sync vs async communication

Common mistake: Starting with microservices for a small team and a small product. You get network failures, distributed transactions and operational cost without the benefit. Start with a well-structured modular monolith and extract services when scaling or team boundaries demand it.

Interview questions

Do I still need Eureka on Kubernetes?

Usually not. Kubernetes gives every Service a DNS name and load balancing, so services can call http://payment-service directly. Config and secrets also come from ConfigMaps and Secrets.

How do I handle a transaction across two services?

You cannot use a database transaction. Use a saga with compensation steps, keep each local transaction small, and make every step idempotent.

What is an API gateway used for?

One entry point for clients: routing, authentication, rate limiting, TLS termination, CORS and response aggregation.