RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
HardSpring Boot#597 min readJul 11, 2026

Circuit Breaker.

spring-boot
practice
learning
microservices
Practice modeTest yourself instead of reading straight through

An outage rarely starts with one big crash; it starts with one slow dependency and a pile of waiting threads.

Question: What is a circuit breaker in Spring Boot?

Answer: A circuit breaker is a safety pattern that stops your app from calling a failing remote service over and over. In Spring Boot, we usually add it with Resilience4j, which watches recent failures and changes state from closed to open and then to half-open. When it opens, the app fails fast or uses a fallback so the whole system stays healthy.

Interview-Ready Answer: In Spring Boot, I use a circuit breaker to protect the app from a bad downstream service. It tracks failures in a sliding window, opens when the failure rate crosses the threshold, and then later allows a few test calls in half-open state. The key benefit is that it fails fast instead of tying up threads, and with Resilience4j I can also return a safe fallback response.

🧠 Memory Map
Memory map — visual summary of this topic

What it is

A circuit breaker is a resilience tool for remote calls. Think of it like an automatic fuse box: if one line keeps sparking, the breaker cuts the power so the whole building does not burn down. In microservices, the "sparks" are repeated timeouts, 5xx responses, or other call failures from another service.

How it works under the hood

  1. Your code calls a method annotated with @CircuitBreaker.
  2. Spring uses an AOP proxy (a wrapper object that intercepts method calls) to check the breaker state before the real method runs.
  3. If the breaker is closed, the call goes through and Resilience4j records whether it succeeded, failed, or was slow.
  4. Those results are tracked in a sliding window (a recent history of calls, by count or time) so the breaker looks at current behavior, not old history forever.
  5. If failures cross the threshold, the breaker flips to open and short-circuits new calls with CallNotPermittedException instead of hitting the downstream service.
  6. After the wait period, the breaker enters half-open and allows a small number of test calls.
  7. If those test calls succeed, the breaker closes again; if they fail, it opens again.

Important default numbers: common Resilience4j defaults are a 100-call sliding window, a 50% failure-rate threshold, 10 half-open test calls, and a 60-second wait in open state. For demos and tests, people usually shrink the window to 5 or 10 calls so the breaker trips quickly.

When and why to use it

Use a circuit breaker around slow or unstable remote dependencies: payment gateways, inventory services, third-party APIs, or even a fragile database call through another service. It is most valuable when waiting longer is worse than failing fast. That is why it is different from retry: retry says "try again"; circuit breaker says "stop trying for a while".

TechniqueWhat it doesBest useTrade-off
Circuit BreakerStops calls after failuresUnstable remote callsFast failover
RetryTries againTemporary glitchesCan add load
TimeoutLimits waiting timeSlow responsesNo future protection
BulkheadLimits concurrencyShared-resource protectionLower throughput

Performance and tuning

The runtime overhead is small. The decision is effectively constant time per call, and memory use is proportional to the window size, so the bookkeeping is O(1) per call with O(N) space for a count-based window of size N. The real tuning knobs are the failure threshold, the minimum number of calls before evaluation, the wait duration in open state, and the slow-call threshold. A common beginner mistake is using the default window in a test and wondering why the breaker never opens soon enough.

Edge cases and gotchas

  • Do not put the breaker on every method; protect only slow, risky remote work.
  • Fallbacks must be quick and safe. If your fallback calls the same failing service, you have just built a loop.
  • Spring AOP does not intercept self-invocation (one method in the same class calling another annotated method), so put the breaker on a Spring bean method that is called from outside the bean.
  • A private method is not a good target for annotation-based proxying, because the proxy only sees calls it can intercept.
  • A circuit breaker does not fix the root cause; it buys time and keeps the blast radius small while you debug the real problem.

Real-world story

Imagine a checkout service calling a payment gateway during a flash sale. For the first minute everything is fine, then the gateway starts taking 8 seconds per request. Without a circuit breaker, Tomcat threads pile up waiting, p99 latency jumps, and users start seeing 504s even for unrelated requests because the server is exhausted.

With a circuit breaker, the checkout service stops hammering the gateway after the failure threshold is reached. It returns a controlled message like "payment temporarily unavailable, please retry" and logs the failure cleanly. That is much better than letting the whole site freeze.

What goes wrong when people misunderstand it: a team treats retry as the whole solution and keeps calling the broken gateway five times per request. The outage gets louder: more traffic, more timeouts, more thread starvation, and sometimes more 5xx responses than before. In logs you often see timeouts first, then CallNotPermittedException once the breaker finally opens.

Spring Boot
package com.example.circuitbreaker;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;\nimport io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.time.Duration;\n\n@SpringBootApplication\npublic class CircuitBreakerDemoApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(CircuitBreakerDemoApplication.class, args);\n    }\n\n    @Bean\n    RestTemplate restTemplate(RestTemplateBuilder builder) {\n        // Short timeouts help the breaker get a quick signal when the downstream is unhealthy.\n        return builder\n                .setConnectTimeout(Duration.ofSeconds(1))\n                .setReadTimeout(Duration.ofSeconds(2))\n                .build();\n    }\n}\n\n@RestController\n@RequestMapping("/inventory")\nclass InventoryController {\n\n    @GetMapping("/{sku}")\n    ResponseEntity<String> inventory(@PathVariable String sku,\n                                     @RequestParam(defaultValue = "false") boolean fail) {\n        // This endpoint simulates a remote dependency that sometimes fails.\n        if (fail || "FAIL".equalsIgnoreCase(sku)) {\n            return ResponseEntity.status(503).body("Inventory service unavailable for sku=" + sku);\n        }\n        return ResponseEntity.ok("inventory-ok for sku=" + sku);\n    }\n}\n\n@RestController\n@RequestMapping("/orders")\nclass OrderController {\n    private final OrderService orderService;\n\n    OrderController(OrderService orderService) {\n        this.orderService = orderService;\n    }\n\n    @GetMapping("/{sku}")\n    String placeOrder(@PathVariable String sku,\n                      @RequestParam(defaultValue = "false") boolean failInventory) {\n        return orderService.placeOrder(sku, failInventory);\n    }\n\n    @GetMapping("/breaker-state")\n    String breakerState() {\n        return orderService.breakerState();\n    }\n}\n\n@Service\nclass OrderService {\n    private final RestTemplate restTemplate;\n    private final CircuitBreakerRegistry circuitBreakerRegistry;\n\n    OrderService(RestTemplate restTemplate, CircuitBreakerRegistry circuitBreakerRegistry) {\n        this.restTemplate = restTemplate;\n        this.circuitBreakerRegistry = circuitBreakerRegistry;\n    }\n\n    @CircuitBreaker(name = "inventoryService", fallbackMethod = "inventoryFallback")\n    public String placeOrder(String sku, boolean failInventory) {\n        // In a real system this would call another microservice. Here we call a local endpoint to keep the demo runnable.\n        String url = "http://localhost:8080/inventory/" + sku + "?fail=" + failInventory;\n        ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);\n\n        // RestTemplate throws for 4xx/5xx, so a 503 becomes a failure signal for the breaker.\n        return "Order accepted. " + response.getBody();\n    }\n\n    // The fallback must match the original arguments, plus the Throwable that explains why the call failed.\n    public String inventoryFallback(String sku, boolean failInventory, Throwable ex) {\n        return "Order accepted with delayed inventory check for sku=" + sku +\n                ". Fallback reason=" + ex.getClass().getSimpleName();\n    }\n\n    String breakerState() {\n        return circuitBreakerRegistry.circuitBreaker("inventoryService").getState().name();\n    }\n}\n

Follow-up & Tricky Questions:

  • How is a circuit breaker different from retry? Retry tries the same call again because the problem might be temporary; a circuit breaker stops calling after repeated failure because the system needs a pause. In real systems, you often use both: a short timeout, a small retry count, and then a breaker.
  • What is half-open state? Half-open is the test mode. The breaker allows a few requests through after the open wait period, and if they succeed it closes; if they fail, it opens again.
  • Where should the annotation go? Put it on the Spring bean method that performs the remote call, usually in a service class. Do not put it only on a controller if the real risk is deep in a downstream client call.
  • How do you tune it in production? Start with the downstream SLA and timeout, then set failure threshold, minimum calls, and open wait to match real traffic. For low-traffic services, use a smaller sliding window; otherwise the breaker may react too slowly.
  • How do you observe it? With Spring Boot Actuator and Micrometer, you can expose breaker state and failure metrics. That helps you see whether the breaker is protecting the system or staying open too long.
  • Tricky: Does a circuit breaker stop the first failure? No. It reacts after the configured threshold is reached, so one error usually just gets counted.
  • Tricky: Will a private method or self-call be intercepted? Usually no. Spring's proxy only wraps calls that come through the bean, so internal calls inside the same class can bypass the annotation.
  • Tricky: Is fallback always safe? Not automatically. A bad fallback can hide real problems, return misleading data, or call the same failing dependency again and make things worse.

Common Mistakes:

  • Using the breaker on local code. Correction: protect remote or fragile operations, not simple in-memory logic.
  • Confusing retry with protection. Correction: retry is for a temporary blip; breaker is for repeated failure and blast-radius control.
  • Making the fallback slow or dependent on the same service. Correction: fallback should be fast, simple, and safe.
  • Forgetting Spring proxy limits. Correction: annotate a public bean method that is called from outside the bean, otherwise the breaker may not fire.

Memory Hook: The breaker is the nightclub bouncer: closed means everyone gets in, open means nobody gets in, and half-open means a few people are tested at the door before the crowd is let back in.

Cheat Sheet:

  • Purpose: fail fast when a downstream service is unhealthy.
  • States: closed, open, half-open.
  • Spring Boot usually uses Resilience4j for the annotation.
  • Pair it with timeout and sometimes retry, but do not over-retry.
  • Fallback should be fast and should not depend on the same broken service.
  • Default Resilience4j settings often use a 100-call window and a 50% failure threshold.

Practice Tasks:

  • Call the demo endpoint with failInventory=true and watch the fallback response.
  • Send repeated failures until the breaker state changes from CLOSED to OPEN, then check /orders/breaker-state.
  • Add a retry around the same call and compare the logs and response time.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

package com.example.circuitbreaker;\n\nimport io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;\nimport io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.time.Duration;\n\n@SpringBootApplication\npublic class CircuitBreakerDemoApplication {\n public static void main(String[] args) {\n SpringApplication.run(CircuitBreakerDemoApplication.class, args);\n }\n\n @Bean\n RestTemplate restTemplate(RestTemplateBuilder builder) {\n // Short timeouts help the breaker get a quick signal when the downstream is unhealthy.\n return builder\n .setConnectTimeout(Duration.ofSeconds(1))\n .setReadTimeout(Duration.ofSeconds(2))\n .build();\n }\n}\n\n@RestController\n@RequestMapping("/inventory")\nclass InventoryController {\n\n @GetMapping("/{sku}")\n ResponseEntity<String> inventory(@PathVariable String sku,\n @RequestParam(defaultValue = "false") boolean fail) {\n // This endpoint simulates a remote dependency that sometimes fails.\n if (fail || "FAIL".equalsIgnoreCase(sku)) {\n return ResponseEntity.status(503).body("Inventory service unavailable for sku=" + sku);\n }\n return ResponseEntity.ok("inventory-ok for sku=" + sku);\n }\n}\n\n@RestController\n@RequestMapping("/orders")\nclass OrderController {\n private final OrderService orderService;\n\n OrderController(OrderService orderService) {\n this.orderService = orderService;\n }\n\n @GetMapping("/{sku}")\n String placeOrder(@PathVariable String sku,\n @RequestParam(defaultValue = "false") boolean failInventory) {\n return orderService.placeOrder(sku, failInventory);\n }\n\n @GetMapping("/breaker-state")\n String breakerState() {\n return orderService.breakerState();\n }\n}\n\n@Service\nclass OrderService {\n private final RestTemplate restTemplate;\n private final CircuitBreakerRegistry circuitBreakerRegistry;\n\n OrderService(RestTemplate restTemplate, CircuitBreakerRegistry circuitBreakerRegistry) {\n this.restTemplate = restTemplate;\n this.circuitBreakerRegistry = circuitBreakerRegistry;\n }\n\n @CircuitBreaker(name = "inventoryService", fallbackMethod = "inventoryFallback")\n public String placeOrder(String sku, boolean failInventory) {\n // In a real system this would call another microservice. Here we call a local endpoint to keep the demo runnable.\n String url = "http://localhost:8080/inventory/" + sku + "?fail=" + failInventory;\n ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);\n\n // RestTemplate throws for 4xx/5xx, so a 503 becomes a failure signal for the breaker.\n return "Order accepted. " + response.getBody();\n }\n\n // The fallback must match the original arguments, plus the Throwable that explains why the call failed.\n public String inventoryFallback(String sku, boolean failInventory, Throwable ex) {\n return "Order accepted with delayed inventory check for sku=" + sku +\n ". Fallback reason=" + ex.getClass().getSimpleName();\n }\n\n String breakerState() {\n return circuitBreakerRegistry.circuitBreaker("inventoryService").getState().name();\n }\n}\n