FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

Spring Boot Microservices: Service Discovery, API Gateway, Circuit Breaker and Config

Build production-ready Spring Boot microservices. Covers Spring Cloud components, Eureka service discovery, API Gateway, circuit breakers with Resilience4j, centralized config, and inter-service communication.

Yusuf SeyitoğluMarch 17, 20260 views12 min read

Spring Boot Microservices: Service Discovery, API Gateway, Circuit Breaker and Config

Building microservices with Spring Boot means more than just splitting a monolith into smaller services. You need service discovery so services can find each other, an API Gateway to handle cross-cutting concerns, circuit breakers for resilience, and centralized configuration. Spring Cloud provides all of these. This guide shows how they fit together.

The Architecture

code
Client β”‚ β–Ό API Gateway (Spring Cloud Gateway) β”‚ -- routes requests, handles auth, rate limiting β”‚ β”œβ”€β”€β–Ί User Service β”œβ”€β”€β–Ί Order Service ──► Product Service (via Feign) └──► Notification Service (via Kafka) Eureka Server (Service Discovery) -- all services register here Config Server (Spring Cloud Config) -- all services fetch config from here

Service Discovery with Eureka

Eureka Server

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

Registering a Service

java
// Each microservice adds this dependency: // spring-cloud-starter-netflix-eureka-client @SpringBootApplication public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
yaml
# application.yml for each service spring: application: name: order-service # this is the service name in Eureka eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true

Inter-Service Communication with Feign

java
// pom.xml: spring-cloud-starter-openfeign @EnableFeignClients @SpringBootApplication public class OrderServiceApplication { ... } // Feign client interface -- no implementation needed @FeignClient(name = "product-service") // matches spring.application.name in product-service public interface ProductClient { @GetMapping("/api/products/{id}") ProductDto getProduct(@PathVariable Long id); @PutMapping("/api/products/{id}/stock") void updateStock(@PathVariable Long id, @RequestBody StockUpdateRequest request); } // Use it like a regular Spring bean @Service @RequiredArgsConstructor public class OrderService { private final ProductClient productClient; private final OrderRepository orderRepository; public Order createOrder(CreateOrderRequest request) { ProductDto product = productClient.getProduct(request.getProductId()); if (product.getStock() < request.getQuantity()) { throw new InsufficientStockException("Not enough stock"); } Order order = new Order(); order.setProductId(request.getProductId()); order.setQuantity(request.getQuantity()); order.setTotalPrice(product.getPrice().multiply( BigDecimal.valueOf(request.getQuantity()) )); Order saved = orderRepository.save(order); productClient.updateStock(request.getProductId(), new StockUpdateRequest(-request.getQuantity())); return saved; } }

Feign + Eureka means you call services by name β€” load balancing is automatic.

Circuit Breaker with Resilience4j

Without circuit breakers, a slow or failing downstream service can cascade failures through your entire system. Resilience4j stops this:

java
// pom.xml: spring-cloud-starter-circuitbreaker-resilience4j @Service @RequiredArgsConstructor public class OrderService { private final ProductClient productClient; @CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback") @Retry(name = "productService") @TimeLimiter(name = "productService") public CompletableFuture<ProductDto> getProduct(Long productId) { return CompletableFuture.supplyAsync(() -> productClient.getProduct(productId)); } public CompletableFuture<ProductDto> getProductFallback(Long productId, Throwable ex) { log.error("Product service unavailable for id {}: {}", productId, ex.getMessage()); return CompletableFuture.completedFuture( ProductDto.builder() .id(productId) .name("Unknown Product") .price(BigDecimal.ZERO) .build() ); } }
yaml
# application.yml resilience4j: circuitbreaker: instances: productService: register-health-indicator: true sliding-window-size: 10 permitted-number-of-calls-in-half-open-state: 3 failure-rate-threshold: 50 # open circuit if 50% of calls fail wait-duration-in-open-state: 10s retry: instances: productService: max-attempts: 3 wait-duration: 500ms timelimiter: instances: productService: timeout-duration: 3s

API Gateway with Spring Cloud Gateway

java
// pom.xml: spring-cloud-starter-gateway @SpringBootApplication public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
yaml
# application.yml server: port: 8080 spring: application: name: api-gateway cloud: gateway: routes: - id: user-service uri: lb://user-service # lb:// means load-balanced via Eureka predicates: - Path=/api/users/** filters: - StripPrefix=0 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200 - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** filters: - name: CircuitBreaker args: name: orderService fallbackUri: forward:/fallback/orders - id: product-service uri: lb://product-service predicates: - Path=/api/products/** - Method=GET # only route GET requests here default-filters: - name: Retry args: retries: 3 methods: GET

JWT Authentication Filter in Gateway

java
@Component public class JwtAuthenticationFilter implements GatewayFilter { private final JwtUtil jwtUtil; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); // Skip auth for public endpoints if (path.startsWith("/api/auth/")) { return chain.filter(exchange); } String authHeader = exchange.getRequest() .getHeaders().getFirst("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } String token = authHeader.substring(7); try { Claims claims = jwtUtil.extractAllClaims(token); // Add user info as header for downstream services ServerHttpRequest mutatedRequest = exchange.getRequest() .mutate() .header("X-User-Id", claims.getSubject()) .header("X-User-Role", claims.get("role", String.class)) .build(); return chain.filter(exchange.mutate().request(mutatedRequest).build()); } catch (JwtException e) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } }

Centralized Configuration with Spring Cloud Config

java
// Config Server // pom.xml: spring-cloud-config-server @EnableConfigServer @SpringBootApplication public class ConfigServerApplication { ... }
yaml
# Config Server application.yml server: port: 8888 spring: cloud: config: server: git: uri: https://github.com/myorg/config-repo default-label: main search-paths: "{application}" -- subfolder per service
yaml
# In config-repo/order-service/application.yml (stored in Git) spring: datasource: url: jdbc:postgresql://prod-db:5432/orders username: orders_user password: ${DB_PASSWORD} # injected from secrets manager order: max-items-per-order: 50 processing-timeout-seconds: 30

Each service fetches its config from the Config Server on startup:

yaml
# Each service bootstrap.yml spring: application: name: order-service config: import: configserver:http://config-server:8888

Asynchronous Communication with Kafka

For operations that do not need immediate responses (notifications, analytics, audit logs):

java
// Order service publishes event after order is created @Service @RequiredArgsConstructor public class OrderService { private final KafkaTemplate<String, OrderEvent> kafkaTemplate; public Order createOrder(CreateOrderRequest request) { Order order = saveOrder(request); OrderEvent event = OrderEvent.builder() .orderId(order.getId()) .userId(order.getUserId()) .total(order.getTotalPrice()) .status("CREATED") .build(); kafkaTemplate.send("order-events", order.getId().toString(), event); return order; } } // Notification service consumes the event @Component @RequiredArgsConstructor public class OrderEventConsumer { private final EmailService emailService; private final UserClient userClient; @KafkaListener(topics = "order-events", groupId = "notification-group") public void handleOrderEvent(OrderEvent event) { User user = userClient.getUser(event.getUserId()); emailService.sendOrderConfirmation(user.getEmail(), event); } }

Common Interview Questions

Q: What is service discovery and why is it needed in microservices?

In a microservices architecture, services need to communicate with each other. But service instances have dynamic IP addresses β€” they scale up and down, crash and restart. Hard-coding IPs is fragile. Service discovery lets each service register itself by name when it starts, and look up other services by name. Eureka maintains this registry; the client-side load balancer (via Feign or RestTemplate) queries it automatically.

Q: What is the circuit breaker pattern and when does it open?

A circuit breaker tracks the failure rate of calls to a downstream service. When failures exceed a threshold (e.g., 50% of the last 10 calls failed), the circuit "opens" β€” subsequent calls immediately return the fallback without hitting the failing service. After a waiting period, the circuit enters "half-open" state and allows a few probe calls. If they succeed, the circuit closes; if they fail, it stays open.

Q: What is the difference between synchronous and asynchronous inter-service communication?

Synchronous (Feign/REST): the caller waits for a response. Simple, immediate feedback, but creates coupling β€” if the called service is slow or down, the caller is affected. Asynchronous (Kafka/messaging): the caller publishes an event and moves on without waiting. Decoupled, resilient, but eventual consistency β€” the called service processes when it is ready.

Practice Java on Froquiz

Spring Boot and microservices architecture are tested in senior Java backend interviews. Test your Java and Spring Boot knowledge on Froquiz β€” covering core Java, Spring, and design patterns.

Summary

  • Eureka Server is the service registry β€” all services register by name on startup
  • Feign clients call other services by name β€” Eureka + load balancing is automatic
  • Resilience4j circuit breaker prevents cascading failures β€” opens when failure rate is too high
  • Spring Cloud Gateway handles routing, auth, rate limiting, and retries at the edge
  • JWT validation in the gateway adds user info as headers β€” downstream services trust these headers
  • Spring Cloud Config stores configuration in Git β€” services fetch on startup, refresh without restart
  • Kafka decouples services for events that do not need immediate responses

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • Python Testing with pytest: Fixtures, Parametrize, Mocking and Best PracticesMar 17
  • Software Architecture Patterns: MVC, Clean Architecture, Hexagonal and Event-DrivenMar 17
  • PostgreSQL Full-Text Search: tsvector, tsquery, Ranking and Multilingual SearchMar 17
All Blogs