Skip to content

What are Spring Cloud Microservices Best Practices in 2026

Problem

I returned to a microservices project after two years away. When I tried to start the application, I got warnings about deprecated Netflix OSS components:

console output
2026-03-11 10:15:23 WARN [main] o.s.c.n.a.s.a.RibbonAutoConfiguration - Ribbon is in maintenance mode. Use Spring Cloud LoadBalancer instead.
2026-03-11 10:15:24 WARN [main] o.s.c.n.zuul.ZuulProxyAutoConfiguration - Zuul is in maintenance mode. Use Spring Cloud Gateway instead.
2026-03-11 10:15:25 WARN [main] o.s.c.n.h.HystrixCircuitBreakerConfiguration - Hystrix is in maintenance mode. Use Resilience4j instead.

I was confused. These components worked fine before. What changed? And what should I use now?

What Happened?

The microservices landscape shifted significantly. Netflix OSS components (Hystrix, Zuul, Ribbon) that were once the standard are now deprecated or in maintenance mode.

Here’s the modern mapping:

Component Evolution
DEPRECATED MODERN ALTERNATIVE
----------- ------------------
Hystrix --> Resilience4j (circuit breaker)
Zuul --> Spring Cloud Gateway (API gateway)
Ribbon --> Spring Cloud LoadBalancer
Archaius --> Spring Cloud Config
Eureka --> Eureka (still maintained) or Kubernetes Services

I made the mistake of using old tutorials. Many online courses still reference deprecated Netflix OSS components.

Environment

  • Spring Boot 3.2.x
  • Spring Cloud 2023.0.x
  • Java 21
  • Kubernetes (optional)

The Solution

I learned to focus on essential patterns first. Here’s my approach.

Step 1: Start with Essential Patterns Only

I used to implement all Spring Cloud patterns immediately. That was wrong. Now I start with the minimum viable architecture:

Minimum Viable Architecture
+-------------------+ +-------------------+
| API Gateway | | Config Server |
| Spring Cloud | | Spring Cloud |
| Gateway | | Config |
+--------+----------+ +-------------------+
|
| Service Discovery (Eureka or K8s)
|
+----+----+ +---------+ +---------+
| Service A| | Service B| | Service C|
+----------+ +----------+ +----------+

Essential patterns (implement these first):

  1. Service Discovery - Never hardcode service URLs
  2. API Gateway - Single entry point for all clients
  3. Centralized Configuration - Externalize all environment-specific config
  4. Circuit Breaker - Prevent cascading failures

Step 2: Use Modern Components

I replaced deprecated components in my pom.xml:

pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<resilience4j.version>2.2.0</resilience4j.version>
</properties>
<dependencies>
<!-- Service Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- API Gateway (NOT Zuul) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Centralized Config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- Circuit Breaker (NOT Hystrix) -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>${resilience4j.version}</version>
</dependency>
</dependencies>

Step 3: Configure Service Discovery

I set up Eureka for service discovery. The key point: never hardcode URLs.

application.yml
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://discovery-server:8761/eureka/
instance:
prefer-ip-address: true
health-check-url-path: /actuator/health

Then I used @LoadBalanced RestTemplate for service-to-service calls:

OrderServiceApplication.java
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class OrderServiceApplication {
@Bean
@LoadBalanced // Spring Cloud LoadBalancer, NOT Ribbon
public RestTemplate restTemplate() {
return new RestTemplate();
}
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
// Use logical service name, NOT hardcoded URL
return restTemplate().getForObject(
"http://inventory-service/api/inventory/" + id,
Order.class
);
}
}

Step 4: Add Circuit Breakers

I added Resilience4j for circuit breakers. This prevents cascading failures.

OrderService.java
@Service
public class OrderService {
@CircuitBreaker(name = "inventoryService", fallbackMethod = "fallbackInventory")
public Inventory checkInventory(Long productId) {
return restTemplate.getForObject(
"http://inventory-service/api/inventory/" + productId,
Inventory.class
);
}
// Fallback when inventory service is down
public Inventory fallbackInventory(Long productId, Exception e) {
return new Inventory(productId, 0, false);
}
}

Configuration for the circuit breaker:

application.yml
resilience4j:
circuitbreaker:
instances:
inventoryService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 3

Step 5: Set Up API Gateway

I used Spring Cloud Gateway instead of deprecated Zuul:

application.yml (API Gateway)
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service # Load-balanced via discovery
predicates:
- Path=/api/orders/**
filters:
- name: CircuitBreaker
args:
name: orderCircuitBreaker
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20

Why This Matters

I made several mistakes before learning these best practices:

Mistake #1: Using deprecated Netflix OSS components These have no active development. No Spring Boot 3 support. Security vulnerabilities pile up.

Mistake #2: Implementing all patterns at once I tried to add distributed tracing, message queues, and service mesh on day one. Debugging was a nightmare.

Mistake #3: Ignoring Kubernetes-native solutions When deploying to Kubernetes, I duplicated functionality. Kubernetes already provides service discovery (Services) and configuration (ConfigMaps/Secrets).

When to Use Kubernetes-Native Solutions

If deploying to Kubernetes, consider using:

k8s-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
ports:
- port: 8080
selector:
app: order-service
---
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
spring.profiles.active: "kubernetes"

Spring Cloud Kubernetes provides seamless integration:

application-kubernetes.yml
spring:
cloud:
kubernetes:
discovery:
enabled: true
config:
enabled: true

Common Mistakes to Avoid

  1. Hardcoding service URLs - Always use service discovery
  2. Missing circuit breakers - Every service-to-service call needs one
  3. Skipping distributed tracing - Add Micrometer + Zipkin from day one
  4. Synchronous communication everywhere - Use message queues for decoupling
  5. Ignoring configuration management - Externalize all environment-specific config

Architecture Checklist

Before considering my microservices architecture complete:

  • Service Discovery configured (Eureka or Kubernetes Services)
  • API Gateway implemented (Spring Cloud Gateway, NOT Zuul)
  • Centralized Configuration (Config Server or Kubernetes ConfigMaps)
  • Circuit Breakers at service boundaries (Resilience4j, NOT Hystrix)
  • Health checks exposed (Spring Boot Actuator)
  • All hardcoded URLs removed
  • Using Spring Cloud LoadBalancer (NOT Ribbon)
  • Spring Boot 3.x compatible dependencies

Summary

I learned that modern Spring Cloud microservices best practices prioritize:

  1. Modern components over deprecated Netflix OSS (Resilience4j, Spring Cloud Gateway, Spring Cloud LoadBalancer)
  2. Essential patterns first, then add advanced features incrementally
  3. Kubernetes-native solutions when deploying to Kubernetes

The key insight: don’t over-engineer. Start with service discovery, API gateway, centralized configuration, and circuit breakers. Add distributed tracing, message queues, and service mesh only when you actually need them.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments