How do you configure a Resilience4j instance in application.yml, and how do fallback methods and default/instance config interact?
answer
- configs.<template> + instances.<name>
- base-config: default then override
- annotation name -> instances lookup, else default
- fallback same signature + trailing Throwable
- self-invocation bypasses the proxy
basics
~20 sYou define named instances under resilience4j.<pattern>.instances.<name> in application.yml (thresholds, window, timeouts), optionally inheriting from a configs.default block. The annotation's name attribute must match the instance name; fallbackMethod names a same-signature method with a trailing Throwable.
solid answer
~40 sEach resilience pattern has its own YAML root: resilience4j.circuitbreaker, .retry, .bulkhead, .ratelimiter, .timelimiter. Under each you have configs.<sharedName> reusable templates and instances.<name> concrete instances. An instance can point to a template via base-config: default and override individual keys. The @CircuitBreaker(name = "x") value selects instances.x; if that instance isn't declared, Resilience4j creates it from the default config (or built-in defaults). fallbackMethod must live in the same bean, have the same parameters plus a trailing Throwable (you can overload for different exception types — the most specific match wins), and return the same type. The fallback fires for any recorded exception and for CallNotPermittedException when the breaker is OPEN. Config is bound at startup via Spring Boot's @ConfigurationProperties, and instances are lazily created on first use.
code
yaml · 24 linesresilience4j:
circuitbreaker:
configs:
default:
sliding-window-type: COUNT_BASED
sliding-window-size: 20
minimum-number-of-calls: 10
failure-rate-threshold: 50
slow-call-rate-threshold: 100
slow-call-duration-threshold: 2s
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 3
register-health-indicator: true
instances:
inventory:
base-config: default
failure-rate-threshold: 60 # override only this key
payment:
base-config: default
retry:
instances:
inventory:
max-attempts: 3
wait-duration: 500msgo deeper
Know instances are named in YAML and the annotation name must match.
Explain configs.default + base-config inheritance, lazy instance creation, and fallback signature rules.
Discuss overloaded fallbacks by exception type, self-invocation proxy pitfalls, and actuator health/metrics wiring.
Design a shared config strategy across many services, externalize thresholds per environment, and govern registry-level defaults and event consumers centrally.
**YAML structure.** Every Resilience4j module the starter auto-configures reads a dedicated properties root: - `resilience4j.circuitbreaker` - `resilience4j.retry` - `resilience4j.bulkhead` (semaphore) and `resilience4j.thread-pool-bulkhead` - `resilience4j.ratelimiter` - `resilience4j.timelimiter` Under each you get two maps: - **`configs`** — named, reusable templates (commonly `default`). - **`instances`** — concrete named instances referenced by the annotation's `name`. An instance reuses a template with `base-config` (circuit breaker/others) and overrides specific keys: ```yaml resilience4j.circuitbreaker: configs: default: sliding-window-size: 20 failure-rate-threshold: 50 wait-duration-in-open-state: 5s instances: inventory: base-config: default failure-rate-threshold: 60 # override just this payment: base-config: default ``` **How the name binds.** `@CircuitBreaker(name = "inventory")` looks up `instances.inventory`. If no such instance is declared, Resilience4j creates one on the fly using the `default` config (if present) or library defaults. This means a typo in the name silently gives you a default-configured breaker rather than an error — a common gotcha. **Lazy creation.** The `CircuitBreakerRegistry` (and the equivalent registries for other patterns) builds each instance lazily on first invocation, then caches it. All calls with the same name share the same stateful instance — the sliding window is shared across threads and requests. **Fallback methods.** `fallbackMethod` must: 1. Be in the **same class/bean** (it's resolved by reflection on the target proxy). 2. Have the **same parameter list** as the protected method, plus a **trailing `Throwable`** (or a more specific exception type). 3. Return the **same type** (or a subtype). You may define **multiple overloaded fallbacks** with different trailing exception types; Resilience4j dispatches to the most specific match for the thrown exception. If no fallback matches, the original exception propagates. ```java private Foo fallback(String id, CallNotPermittedException e) { ... } // breaker open private Foo fallback(String id, Throwable t) { ... } // everything else ``` **Gotchas.** - Fallbacks are invoked via the AOP proxy — a **self-invocation** (calling the annotated method from within the same bean) bypasses the proxy and the annotation has no effect. - The fallback swallows the exception; if it just rethrows, the breaker still records the original failure. - Config keys are kebab-case in YAML but map to camelCase builder properties. - `resilience4j.circuitbreaker.instances.x.register-health-indicator: true` wires it into `/actuator/health`. **When to use `configs.default`.** When many instances share the same policy — define once, reference by `base-config`, override per instance. It keeps YAML DRY and gives new instances sane defaults automatically.
- What happens if @CircuitBreaker(name="typo") references an instance not declared in YAML?No error is raised. Resilience4j lazily creates an instance named 'typo' using the configs.default template if present, otherwise library defaults. That's why misconfiguration can silently give unexpected behavior.
- Why might a fallbackMethod never be invoked even though the annotation is present?Most often self-invocation: calling the annotated method from within the same bean skips the Spring AOP proxy, so no aspect runs. Also, a mismatched fallback signature (wrong params or missing trailing Throwable) causes a resolution failure.
saying these in an interview costs you the question
- Thinking each pattern shares one global YAML config key rather than per-pattern roots
- Believing an unmatched annotation name throws at startup
- Putting the fallback method in a different class and expecting it to resolve
- Assuming self-invoked annotated methods are still intercepted