Spring Boot
Spring Cloud Microservices: Config Server, Eureka, Gateway, OpenFeign and Resilience4j
Spring Cloud explained with working config: release trains, Config Server, Eureka, Gateway, OpenFeign and Resilience4j, plus when Kubernetes replaces them.
What Is Spring Cloud and Do You Need It?
Spring Cloud is not one framework. It is a collection of separate projects that solve common problems once an application is split into several services: where configuration lives, how services find each other, how requests enter the system, how one service calls another, and what happens when that call fails. You choose the pieces you need; you never adopt all of it.
Use it when you run microservices on plain VMs or a mix of environments, when you want Spring-native solutions for routing and resilience, or when you already have a Spring Boot codebase and need a few cross-cutting features. You may need much less of it if you run entirely on Kubernetes, because the platform already provides discovery, configuration, and load balancing. The table below shows which problem each project solves and the common alternative.
Rule of thumb: Resilience4j, Micrometer Tracing, and a typed HTTP client are useful everywhere. Eureka and Config Server are mostly for non-Kubernetes setups. Gateway is useful in both when you need application-level filters.
Text
Problem Spring Cloud piece Alternative
------------------------------ ---------------------------------- ---------------------------------
Central configuration Config Server Kubernetes ConfigMap / Secret
Finding service instances Eureka (+ LoadBalancer) Kubernetes Service + DNS
Single entry point, routing Spring Cloud Gateway Ingress / Gateway API / Envoy
Calling another service OpenFeign or HTTP Interface client RestClient, WebClient
Slow or failing dependencies Resilience4j circuit breaker Service mesh policies
Following one request Micrometer Tracing OpenTelemetry agentRelease Trains and the BOM
Spring Cloud is versioned as release trains (named by year, such as 2023.0.x or 2025.0.x), and each train works with specific Spring Boot versions. A mismatched pair is the most common cause of baffling startup errors, so choose the train first from the compatibility table on the Spring Cloud project page, then import its BOM. Individual starters then need no version.
Not every service needs every starter. Treat Eureka Server, Config Server, and the Gateway as separate small applications, and give your business services only the client starters they use. The default Spring Cloud Gateway is built on WebFlux, so it cannot share an application with spring-boot-starter-web.
Two naming notes. Newer trains (2025.0 and later) rename the Gateway starters to spring-cloud-starter-gateway-server-webflux and spring-cloud-starter-gateway-server-webmvc, and the configuration prefix becomes spring.cloud.gateway.server.webflux, so check the docs for your train. And Sleuth is gone from Spring Boot 3: tracing is Micrometer Tracing.
Boot to train mapping (verify on spring.io/projects/spring-cloud before you pin): Boot 3.0 to 3.1 uses 2022.0, Boot 3.2 to 3.3 uses 2023.0, Boot 3.4 uses 2024.0, Boot 3.5 uses 2025.0, and Boot 4.0 uses 2025.1.
XML
<!-- pom.xml: import the BOM once -->
<properties>
<java.version>21</java.version>
<!-- Example value: use the latest patch of the train that matches YOUR Spring Boot version -->
<spring-cloud.version>2025.0.0</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>
<!-- A typical BUSINESS service (user-service, order-service, ...) -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <!-- register + discover -->
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId> <!-- pull central config -->
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <!-- call other services -->
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
</dependencies>
<!-- Separate apps, each with its own pom:
config-server -> spring-cloud-config-server
discovery -> spring-cloud-starter-netflix-eureka-server
api-gateway -> spring-cloud-starter-gateway (WebFlux; do NOT add spring-boot-starter-web) -->How the Pieces Fit Together
Gateway decides where an incoming request goes. Eureka tells services where other services are running right now. Config Server hands each service its configuration. LoadBalancer picks one instance of a service. OpenFeign is the client that calls another service. Resilience4j decides what happens when that call is slow or failing. Micrometer Tracing follows one request across all of them.
Before adopting the infrastructure pieces, ask whether your platform already provides them. On Kubernetes, a Service name resolves through DNS, ConfigMaps and Secrets hold configuration, and an ingress handles routing. Running Eureka and Config Server on top duplicates that and adds two more systems to keep alive. Spring Cloud Kubernetes bridges the gap if you want Spring-native access to those features.
Text
Client
|
v
Spring Cloud Gateway routing, token checks, rate limiting (own app)
| lb://user-service
v
user-service <-- Feign + LoadBalancer + Resilience4j --> order-service
| registers / looks up |
+----------------------> Eureka Server <--------------------+ (own app)
Config Server (own app) serves each service's application.yml from Git at startup.
Equivalent on Kubernetes:
Eureka -> Service + DNS (http://user-service)
Config Server -> ConfigMap / Secret
Gateway -> Ingress or Gateway API (or keep Spring Cloud Gateway for app-level filters)Config Server: Serving Configuration from Git
Config Server serves configuration files from a Git repository (or other backends) over HTTP, so one change can reach every service and you get history and code review for free. The repository root holds shared defaults, and per-service files override them.
To enable it, add @EnableConfigServer to the main class of the config-server application, then point it at your repository as shown. Setting clone-on-start makes the server fail at startup if the repository is unreachable, which is better than discovering it when the first client connects.
Secure it. The server hands out database passwords, so put Spring Security in front of it and give clients credentials. Never commit secrets in plain text; Config Server can store them encrypted as {cipher}... values.
yaml
# config-server application.yml
# The main class needs @SpringBootApplication and @EnableConfigServer
server:
port: 8888
spring:
security:
user:
name: config-client
password: ${CONFIG_SERVER_PASSWORD}
cloud:
config:
server:
git:
uri: https://github.com/your-org/microservices-config
default-label: main
search-paths: "{application}" # root is searched too, so shared defaults still apply
clone-on-start: true # fail at startup if the repo is unreachable
# Repo layout
# application.yml shared by every service
# user-service/application.yml
# user-service/application-prod.yml
# order-service/application.yml
# Debug what the server returns for a service and profile:
# GET http://localhost:8888/user-service/prodConfig Client: spring.config.import Instead of bootstrap.yml
On the client, forget bootstrap.yml. It is the old mechanism and now needs an extra starter (spring-cloud-starter-bootstrap). Since Spring Boot 2.4 you declare the server in your normal application.yml with spring.config.import.
Use the plain configserver: form if the app must not start without its configuration (it fails fast), or optional:configserver: if it can run on local defaults. Set the active profile from the environment (SPRING_PROFILES_ACTIVE) rather than hardcoding it in the file.
Plan for refresh: changing the Git repository does not alter running services. Annotate beans with @RefreshScope (or use @ConfigurationProperties, which rebinds), expose the refresh endpoint, and call it. To refresh every instance at once, use Spring Cloud Bus.
yaml
# application.yml in each business service (no bootstrap.yml)
spring:
application:
name: user-service # selects the user-service/* files
config:
import: "configserver:http://localhost:8888" # use optional:configserver: to start without the server
cloud:
config:
username: config-client
password: ${CONFIG_SERVER_PASSWORD}
# Runtime refresh
management:
endpoints:
web:
exposure:
include: health,refresh
# POST /actuator/refresh, then beans marked @RefreshScope pick up the new valuesService Discovery with Eureka
With Eureka, each service registers itself under its spring.application.name, and other services ask the registry for the live instances. Registration is automatic once the Eureka client starter is on the classpath; the @EnableDiscoveryClient annotation is no longer needed. The Eureka server application itself needs @EnableEurekaServer on its main class.
Know its behaviour so it does not surprise you. The registry is eventually consistent: instances renew a lease every 30 seconds by default and clients cache the registry, so a new or dead instance can take up to about a minute to be noticed. Eureka also has self-preservation mode, which stops it evicting instances when many heartbeats go missing at once. In development this makes stopped services appear alive, so many teams turn it off locally and leave it on in production.
Secure the registry (the dashboard and API are open by default), run at least two Eureka servers peering with each other in production, and use prefer-ip-address on containers so instances do not register unreachable hostnames.
yaml
# eureka server application.yml (main class: @SpringBootApplication + @EnableEurekaServer)
server:
port: 8761
eureka:
client:
register-with-eureka: false # single-node setup: the server does not register with itself
fetch-registry: false
server:
enable-self-preservation: false # dev only; leave true in production
# Every business service
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://eureka-user:${EUREKA_PASSWORD}@localhost:8761/eureka
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 30 # default; lower it to detect failures fasterSpring Cloud Gateway: Routes, Filters and Retries
Spring Cloud Gateway is the single entry point: it matches a request with a predicate (path, header, method), applies filters, and forwards it. A uri of lb://user-service tells it to resolve the name through discovery and load-balance across instances.
Use it for cross-cutting edge concerns such as routing, token authentication, rate limiting, header changes, and circuit breaking. Keep business logic out of it. Gateway authentication does not replace authorization inside services; anything reachable on the internal network must still check who is calling.
Be careful with retries. Only retry idempotent methods such as GET, as in the example, or you can duplicate orders and payments.
yaml
# api-gateway application.yml (WebFlux gateway; newer trains use the prefix
# spring.cloud.gateway.server.webflux instead of spring.cloud.gateway)
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service # resolved through Eureka and load-balanced
predicates:
- Path=/api/v1/users/**
filters:
- name: CircuitBreaker
args:
name: userServiceCb
fallbackUri: forward:/fallback/users
- name: Retry
args:
retries: 2
methods: GET # never retry POST blindly
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/v1/orders/**
filters:
- StripPrefix=0
- AddRequestHeader=X-Gateway, edgeOpenFeign Client with a Safe Fallback
OpenFeign turns an interface into an HTTP client: you declare methods with Spring MVC annotations and Feign generates the implementation. With Eureka and LoadBalancer on the classpath, the name in @FeignClient is resolved to real instances. Add @EnableFeignClients to the application class.
Fallbacks need care. A fallback that returns made-up data is dangerous: a fake user named "Unknown" would let an order be created for a person who does not exist. Fall back with a value only where a degraded answer is genuinely acceptable (an empty recommendations list), and for critical data fail clearly. Use fallbackFactory rather than fallback so you can see and log the cause. Fallbacks only run when spring.cloud.openfeign.circuitbreaker.enabled is true.
Retries: Spring Cloud disables Feign's own retrying by default, which is the safe choice for POST.
Java
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/api/v1/users/{id}")
UserResponse findById(@PathVariable("id") Long id);
@GetMapping("/api/v1/users")
List<UserResponse> findAll(@RequestParam(name = "role", required = false) String role);
}
@Component
@Slf4j
class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public UserResponse findById(Long id) {
log.warn("user-service failed for id {}", id, cause);
// Critical data: fail clearly instead of inventing a user
throw new ServiceUnavailableException("user-service is unavailable", cause);
}
@Override
public List<UserResponse> findAll(String role) {
log.warn("user-service list failed", cause);
return List.of(); // acceptable degradation for a non-critical listing
}
};
}
}
// Compile with -parameters (Spring Boot's build plugins do) or give
// @PathVariable / @RequestParam explicit names.OpenFeign Timeouts: Do Not Trust the Defaults
Set Feign timeouts explicitly. The defaults are generous (around 10 seconds to connect and 60 to read), so one slow dependency can tie up your threads for a minute. Choose a read timeout that matches your slowest acceptable response, then let the circuit breaker open when calls keep exceeding it.
Configure a default for all clients and override a single client by its name only when it truly needs a different value.
yaml
# application.yml
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true # required for fallbacks to run
client:
config:
default:
connect-timeout: 2000 # ms
read-timeout: 5000 # ms
user-service: # per-client override (optional)
read-timeout: 3000The Modern Alternative: HTTP Interface Clients
Spring Cloud OpenFeign has moved to maintenance mode (check the project page for its current status), and Spring Framework's own HTTP Interface clients are the direction for new code. You declare an interface with @HttpExchange methods and back it with a RestClient. Building that RestClient from a @LoadBalanced builder keeps the service-name resolution (http://user-service) and the load balancing you had with Feign.
Feign remains fine in existing systems; there is no need to rewrite working clients just for fashion. Use the HTTP Interface style for new services, and pair it with Resilience4j for timeouts and breakers.
Java
@Configuration
class UserClientConfig {
@Bean
@LoadBalanced
RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
@Bean
UserClient userClient(@LoadBalanced RestClient.Builder builder) {
RestClient restClient = builder.baseUrl("http://user-service").build();
return HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(restClient))
.build()
.createClient(UserClient.class);
}
}
@HttpExchange("/api/v1/users")
interface UserClient {
@GetExchange("/{id}")
UserResponse findById(@PathVariable Long id);
}Resilience4j Circuit Breaker Configuration
A circuit breaker watches calls to a dependency. When the failure rate crosses a threshold it opens, and further calls fail immediately instead of waiting on a struggling service. After a pause it lets a few trial calls through (half-open) and closes again if they succeed. This stops one slow service from exhausting threads across the whole system.
Configure it once under configs.default and every breaker inherits it, then override per instance where needed. Pair it with a time limiter, because a circuit breaker only counts calls that finish: a hung call that never returns is never counted as a failure without a timeout.
Monitor it. Resilience4j publishes breaker state and call metrics through Micrometer, so you can alert on a breaker that stays open.
yaml
resilience4j:
circuitbreaker:
configs:
default:
sliding-window-size: 20 # judge the last 20 calls
minimum-number-of-calls: 10 # do not trip on too little data
failure-rate-threshold: 50 # percent
slow-call-duration-threshold: 3s
slow-call-rate-threshold: 80
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
automatic-transition-from-open-to-half-open-enabled: true
timelimiter:
configs:
default:
timeout-duration: 4s
# State and metrics: GET /actuator/circuitbreakers (expose it)
# or the resilience4j_circuitbreaker_* metersCommon Spring Cloud Errors and How to Fix Them
Most Spring Cloud problems fall into a handful of repeating causes: a Boot and train mismatch, a missing starter, the wrong web stack, or a service name that does not match what is registered. The list below shows the messages people search for, what causes them, and the fix.
Text
Error / symptom Cause Fix
---------------------------------------------------------- -------------------------------------------- --------------------------------------------
"Spring MVC found on classpath, which is incompatible Gateway (WebFlux) and spring-boot-starter-web Remove spring-boot-starter-web from the gateway
with Spring Cloud Gateway" in the same app app, or set web-application-type=reactive
"Load balancer does not contain an instance for the Service not registered, name mismatch, or Check spring.application.name and the Eureka
service user-service" registry not yet refreshed dashboard; wait one lease cycle
"No Feign Client for loadBalancing defined. Did you Missing LoadBalancer on the classpath Add spring-cloud-starter-loadbalancer
forget to include spring-cloud-starter-loadbalancer?" (or Eureka client starter)
Config not loaded, app starts with defaults bootstrap.yml used without the bootstrap Use spring.config.import in application.yml
starter instead
App fails at startup: cannot reach config server configserver: import is fail-fast Start Config Server first, or use
optional:configserver:
Config changed in Git but the service still uses old No refresh happened Expose /actuator/refresh, use @RefreshScope,
values or Spring Cloud Bus
Strange NoSuchMethodError / bean errors on startup Spring Boot and Spring Cloud train mismatch Re-check the compatibility table and the BOM
version
Stopped service still appears UP in Eureka Self-preservation mode or lease timing Expected behaviour; disable only in dev
Fallback never runs Circuit breaker support not enabled spring.cloud.openfeign.circuitbreaker.enabled=trueProduction Checklist
Before you ship a Spring Cloud based system, walk through this list. Most production incidents with these components trace back to one of these items being skipped: unsecured infrastructure endpoints, default timeouts, retries on non-idempotent calls, and fallbacks that hide real failures.
Text
[ ] Spring Boot version and Spring Cloud train match the official compatibility table
[ ] BOM imported once; no hardcoded versions on individual Spring Cloud starters
[ ] Gateway, Config Server and Eureka run as separate applications
[ ] Config Server and Eureka are secured; secrets are encrypted or injected from the environment
[ ] At least two Eureka servers peering in production; self-preservation left on
[ ] Clients use spring.config.import, not bootstrap.yml
[ ] Feign / HTTP client connect and read timeouts set explicitly
[ ] Time limiter configured alongside every circuit breaker
[ ] Fallbacks return data only where degraded answers are acceptable; critical paths fail clearly
[ ] Retries limited to idempotent methods (GET)
[ ] Breaker state, error rate and latency exported through Micrometer and alerted on
[ ] Tracing configured with Micrometer Tracing (not Sleuth)
[ ] On Kubernetes: confirmed that Eureka and Config Server are really neededFrequently Asked Questions
Is Spring Cloud the same as microservices?
No. Microservices is an architecture style. Spring Cloud is a set of tools that helps you build it with Spring Boot. You can build microservices without Spring Cloud, and you can use parts of Spring Cloud in a modular monolith.
Do I need Eureka if I use Kubernetes?
Usually not. Kubernetes Services and DNS already provide discovery and load balancing. Use Eureka mainly on VMs or mixed environments.
Do I still need bootstrap.yml for Config Server?
No. Since Spring Boot 2.4, use spring.config.import in application.yml. bootstrap.yml only works with the extra bootstrap starter and is considered legacy.
Can I put Spring Cloud Gateway and Spring MVC in the same application?
Not with the default WebFlux gateway. Run the gateway as its own application, or use the servlet-based Gateway MVC variant if you need a blocking stack.
Is Netflix Hystrix still used?
No. Hystrix was removed from Spring Cloud. Use Resilience4j through Spring Cloud CircuitBreaker.
Is OpenFeign deprecated?
It is in maintenance mode, which means no new features. It still works and is fine for existing systems; HTTP Interface clients are the recommended direction for new code.
Which Spring Cloud version works with my Spring Boot version?
It depends on the release train. Check the compatibility table on spring.io/projects/spring-cloud and import the matching BOM.
Is Sleuth still available?
Not for Spring Boot 3 and later. Use Micrometer Tracing.