Eureka UP, Gateway 502 Drops — Spring Boot Microservices
- Microservices with Spring Boot and Spring Cloud is a core concept for building distributed systems that are resilient and scalable.
- Spring Boot builds the 'what' (the logic), while Spring Cloud manages the 'how' (the communication and coordination).
- Service Discovery (Eureka) and API Gateways (Spring Cloud Gateway) are the entry-level requirements for any microservices architecture.
- Core concept: decomposing applications into independently deployable services with Spring Boot (individual services) and Spring Cloud (coordination layer)
- Service Discovery (Eureka): services register and discover each other by logical name, not hardcoded IPs
- API Gateway (Spring Cloud Gateway): single entry point for routing, auth, rate limiting
- Circuit Breakers (Resilience4j): isolate failures to prevent cascading crashes
- Performance insight: Eureka heartbeats add ~2ms per instance; gateway adds ~5-10ms per request
- Production insight: without healthchecks on depends_on, your gateway routes to dead containers silently
5-Minute Microservices Debug Cheat Sheet
Service not registering with Eureka
curl -v http://<eureka-host>:8761/eureka/apps/<service-name> | jq .docker logs <eureka-container> --tail 50Gateway routes but returns 500 or timeout
curl -w '%{http_code}' http://<service-ip>:<port>/actuator/healthdocker compose logs gateway-service --tail 100 | grep ERRORFeign client call fails with RetryableException
curl http://<service-ip>:<port>/actuator/metrics/jvm.threads.livecurl http://<service-ip>:<port>/actuator/health | jq .components.db.statusProduction Incident
Production Debug GuideSymptom → Action matrix for common microservices problems
Microservices with Spring Boot and Spring Cloud is a fundamental concept in modern Java development. Moving away from the 'Monolithic' architecture—where every feature lives in a single code base—to a distributed system allows for independent scaling, faster deployment cycles, and technological flexibility. However, it introduces the 'Distributed System Tax': complexity in networking, security, and data consistency.
In this guide, we'll break down exactly what Microservices with Spring Boot and Spring Cloud is, why it was designed this way, and how to use it correctly in real projects by leveraging Service Discovery, Configuration Management, and API Gateways. We focus on the Spring Cloud 2023.x release train (Leyton), ensuring your stack is ready for modern cloud environments.
By the end, you'll have both the conceptual understanding and practical code examples to use Microservices with Spring Boot and Spring Cloud with confidence, moving from a single JAR to a resilient, interconnected ecosystem.
What Is Microservices with Spring Boot and Spring Cloud and Why Does It Exist?
Microservices with Spring Boot and Spring Cloud is a core feature-set for building resilient distributed systems. While Spring Boot makes it trivial to create a standalone 'service' (like an Order Service or User Service), Spring Cloud provides the glue. It solves the problem of 'Service Discovery' (how Service A finds Service B without hardcoding IP addresses) and 'Circuit Breaking' (how to stop a failure in one service from cascading and crashing your entire ecosystem).
It exists because managing 50+ independent services manually is impossible; you need an automated infrastructure to handle the overhead. By using declarative tools like OpenFeign, Service A can call Service B just by using its name, while Eureka handles the dynamic IP mapping in the background.
package io.thecodeforge.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * io.thecodeforge Standard: High-Availability Microservice Setup * @EnableDiscoveryClient: Registers this service with Eureka/Consul so others can find it. * @EnableFeignClients: Enables declarative REST clients to call other services via name. */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } // --- Declarative Client Example --- package io.thecodeforge.orderservice.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * Using Feign for type-safe inter-service communication. * Hardcoded URLs are replaced by logical service names. */ @FeignClient(name = "inventory-service") public interface InventoryClient { @GetMapping("/api/inventory/{skuCode}") boolean isInStock(@PathVariable("skuCode") String skuCode); }
INFO: Feign Client 'inventory-service' initialized for OrderService.
Common Mistakes and How to Avoid Them
When learning Microservices with Spring Boot and Spring Cloud, most developers hit the same set of gotchas. A major one is the 'Distributed Monolith,' where services are so tightly coupled that you can't update one without updating them all.
Another is neglecting 'Observability'—if a request fails across four different services, how do you trace it? Using tools like Micrometer Tracing (the successor to Sleuth) and Zipkin is non-negotiable for production debugging. Furthermore, failing to implement Circuit Breakers means that if your Payment Service hangs, your entire Checkout process will also hang until the connection times out.
# Centralized Config Example via Spring Cloud Config spring: application: name: order-service config: import: "optional:configserver:http://forge-config-server:8888" cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/api/order/** # Production-Grade Resilience4j Circuit Breaker Config resilience4j.circuitbreaker: instances: inventoryService: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 5 permittedNumberOfCallsInHalfOpenState: 3 waitDurationInOpenState: 10s failureRateThreshold: 50 eventConsumerBufferSize: 10
// Resilience4j: Circuit breaker 'inventoryService' is monitoring service health.
Configuring Service Discovery with Eureka and Load Balancer
Service Discovery is the backbone of microservices communication. Without it, you're hardcoding IPs and ports, which breaks the moment you scale or redeploy. Netflix Eureka is the battle-tested service registry. Each service registers with its instance ID, hostname, port, and health status. The Spring Cloud LoadBalancer (replacement for Netflix Ribbon) picks a healthy instance using round-robin or custom rules.
Here's the catch: Eureka uses a heartbeat mechanism (30s by default). If a service misses three heartbeats, Eureka removes it. But a service can be 'UP' in Eureka while being completely broken — that's why custom health indicators are critical. Always implement a health endpoint that checks database, message queue, and external API availability.
Another pitfall: self-preservation mode. If Eureka loses too many heartbeats network-wide (e.g., due to a transient network partition), it stops evicting services to protect against false removals. That means your gateway might route to dead instances for minutes. Tune eureka.server.renewalPercentThreshold based on your cluster size.
package io.thecodeforge.gateway; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class GatewayConfig { @Bean @LoadBalanced public RestTemplate loadBalancedRestTemplate() { return new RestTemplate(); } } // In application.yml: // spring: // cloud: // gateway: // routes: // - id: inventory-route // uri: lb://inventory-service // predicates: // - Path=/api/inventory/**
// Gateway routes to 'lb://inventory-service' using client-side load balancing.
- Registration: Service sends POST /eureka/apps/INSTANCE with metadata (host, port, status).
- Renewal: Service sends PUT /eureka/apps/APP/INSTANCE every 30s (heartbeat).
- Eviction: Eureka removes instances that miss 3 consecutive heartbeats (90s window).
- Self-preservation: If heartbeats drop >15% network-wide, Eureka stops evictions to avoid false negatives during network partitions.
API Gateway: The Front Door to Your Microservices
An API Gateway is a single entry point that handles cross-cutting concerns: authentication, rate limiting, request/response transformation, and routing. Spring Cloud Gateway is the current standard — it's reactive (built on WebFlux) and non-blocking, meaning it can handle thousands of concurrent requests with minimal threads.
Why not a plain load balancer? A load balancer distributes traffic but doesn't understand HTTP semantics. A gateway can inspect paths, rewrite URLs, add headers, enforce rate limits per client, and handle CORS. It's also the perfect place to implement token validation (JWT) before requests reach your services, reducing duplicate auth logic.
Key configuration pitfall: route order matters. The gateway evaluates routes in the order they're defined. A generic catch-all route before a specific one will shadow all requests. Always declare specific routes first, then a fallback.
Performance note: Spring Cloud Gateway adds ~5-10ms per request in most cases. If you need sub-millisecond latency, consider running a dedicated sidecar proxy like Envoy instead. But for 95% of applications, the gateway is fine.
# Spring Cloud Gateway Configuration spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/api/users/** filters: - StripPrefix=1 - RemoveRequestHeader=Cookie - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** filters: - name: CircuitBreaker args: name: orderCircuitBreaker fallbackUri: forward:/fallback/orders - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20
// Circuit breaker with fallback for order routes.
// Rate limiter: 10 requests/second per user (Redis-backed).
Resilience: Circuit Breakers, Retries, and Bulkheads
In a distributed system, failures are not exceptions — they're the default. Resilience4j is the de facto library for Spring Boot applications. It provides three core patterns: - Circuit Breaker: monitors failures and opens the circuit when a threshold is hit, preventing further calls to a failing service. After a wait duration, it half-opens and tests the water. - Retry: automatically retries failed calls with exponential backoff, but must be used with an idempotent API (e.g., GET, PUT with idempotency key). - Bulkhead: limits concurrent calls to a service, preventing resource exhaustion from spilling over.
The biggest production mistake: configuring retry without a circuit breaker. If the downstream service is down, retries just amplify the load and delay the failure. Retries are for transient errors (timeouts, 503s), not for persistent failures.
Another trap: thread pool vs semaphore isolation. Resilience4j offers both. Thread pool isolation creates a separate thread pool per circuit breaker (isolated but resource-heavy). Semaphore isolation is lightweight but shares threads with the caller — a blocking downstream can still starve your application. Prefer thread pool isolation for critical paths.
Metrics to monitor: resilience4j.circuitbreaker.state.{name} and resilience4j.circuitbreaker.calls.{name} in Micrometer. Alert on OPEN state lasting more than 5 minutes.
# Resilience4j circuit breaker with retry and bulkhead resilience4j.circuitbreaker: instances: inventoryService: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 5 permittedNumberOfCallsInHalfOpenState: 3 waitDurationInOpenState: 10s failureRateThreshold: 50 eventConsumerBufferSize: 10 resilience4j.retry: instances: inventoryService: maxRetryAttempts: 3 waitDuration: 500ms retryExceptions: - org.springframework.web.client.HttpServerErrorException resilience4j.bulkhead: instances: inventoryService: maxConcurrentCalls: 5 maxWaitDuration: 100ms
// Retries only on 5xx errors, not 4xx.
// Bulkhead limits concurrent calls to 5, with 100ms max wait.
resilience4j.timelimiter) to cap the response time at 500ms and treat timeout as a failure in the circuit breaker.| Aspect | Monolithic Architecture | Microservices (Spring Cloud) |
|---|---|---|
| Deployment | Single unit; all or nothing deployment. | Independent units; deploy features separately. |
| Scaling | Scale the whole app (Vertical/Horizontal). | Scale only the bottleneck service (Granular). |
| Fault Tolerance | One bug can crash the entire process. | Circuit breakers isolate failures to one service. |
| Tech Stack | Locked into one language/framework. | Polyglot-friendly; use the best tool for each service. |
| Data Management | Single shared Database (Strong Consistency). | Database per Service (Eventual Consistency). |
🎯 Key Takeaways
- Microservices with Spring Boot and Spring Cloud is a core concept for building distributed systems that are resilient and scalable.
- Spring Boot builds the 'what' (the logic), while Spring Cloud manages the 'how' (the communication and coordination).
- Service Discovery (Eureka) and API Gateways (Spring Cloud Gateway) are the entry-level requirements for any microservices architecture.
- Never skip observability; distributed tracing (Micrometer) and centralized logging (ELK/Prometheus) are your only hope when debugging a request across multiple service boundaries.
- Adopt the 'Database per Service' rule to ensure truly independent deployments.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QExplain the 'Service Registry' pattern. How does Eureka distinguish between a service being 'Down' vs a 'Network Partition'?SeniorReveal
- QWhat is the role of an API Gateway (like Spring Cloud Gateway) regarding cross-cutting concerns like Authentication and Rate Limiting?Mid-levelReveal
- QHow do you ensure data consistency across multiple microservices without using distributed transactions (2PC)? Discuss the SAGA Pattern.SeniorReveal
- QExplain the 'Circuit Breaker' states (Closed, Open, Half-Open) in Resilience4j and how they protect a Spring Boot system.Mid-levelReveal
- QWhat is 'Client-Side Load Balancing' (LoadBalancer library) and how does it differ from a traditional Hardware Load Balancer?SeniorReveal
Frequently Asked Questions
Do I need both Eureka and an API Gateway?
Yes, they serve different purposes. Eureka handles service registration and discovery (internal service-to-service communication). The API Gateway is the external entry point that routes client requests to services using Eureka's registry. You can skip the gateway for internal-only services, but for any external-facing system, the gateway is essential for security and cross-cutting concerns.
Can I use Consul instead of Eureka?
Absolutely. Spring Cloud supports Consul, ZooKeeper, and Kubernetes-native service discovery. Consul adds key-value store and health checking beyond Eureka. Switch by changing the dependency from spring-cloud-starter-netflix-eureka-client to spring-cloud-starter-consul-discovery. The Feign and Gateway integration works the same way.
How many services is too many for Spring Cloud?
Spring Cloud can scale to hundreds of services on a single Eureka server if properly tuned. But at serious scale (thousands of instances), you'll need to move to a service mesh (Istio, Linkerd) and use Spring Cloud primarily for business logic. Monitor Eureka's heartbeat load and consider partitioning services into multiple Eureka servers or use Kubernetes-native discovery for larger clusters.
Should I share configuration across services via Spring Cloud Config?
Yes, for non-sensitive configuration. Use a Git-backed config server for versioning and audit trails. But never store secrets (passwords, API keys) in the config repo — use a vault (HashiCorp Vault, AWS Secrets Manager) and integrate via spring-cloud-config's encryption or the bootstrap.properties approach. Spring Cloud Config is optional; many teams manage config via environment variables in containers.
What's the best way to handle distributed tracing?
Use Micrometer Tracing (successor to Spring Cloud Sleuth) with Zipkin for trace collection and Jaeger for visualisation. Add spring-cloud-starter-sleuth (or micrometer-tracing-bridge-otel for OpenTelemetry) and configure a sampler (e.g., 10% of requests in production). Tune the span reporting endpoint: use Kafka as a buffer instead of direct HTTP to avoid blocking the application when Zipkin is slow. Without distributed tracing, debugging a request crossing 5+ services is nearly impossible.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.