Skip to content
resilience4cata
ENES

Changelog

Notable changes in each release of resilience4cata, newest first.

The format follows Keep a Changelog, and the project uses Semantic Versioning. Until 1.0.0, a minor version may change the API.

[0.10.0] - 2026-10-27 :id=0.10.0

Added

  • @PolicyOrder: change the order of a method's policies. It lists layers (Layer.RETRY, TIMEOUT, CIRCUIT_BREAKER, …) that move together, in the listed order, to the place of the outermost of them; the others keep their default order. @PolicyOrder({TIMEOUT, RETRY}) puts one timeout around the whole retry loop, {CIRCUIT_BREAKER, RETRY} records one outcome per operation in the breaker, {BULKHEAD, RETRY} holds the slot across retries. On a method, a type or a bundle; decided at compile time, and the fallback stays outermost. Listing a layer twice is R4C-ORD-001. Without it, or when it changes nothing, the generated code is unchanged. A Pipeline keeps the default order. See Changing the order.

[0.9.0] - 2026-10-27 :id=0.9.0

Changed

  • Reactive return types are a compile error. A method with resilience annotations that returns a lazy reactive type (Flow.Publisher or Reactive Streams Publisher, so Reactor's Mono and Flux; Mutiny's Uni and Multi; RxJava's types) used to compile as a synchronous method, so the policies protected only building the pipeline and a failure was never retried or timed out. It is now R4C-ASY-002; return a CompletionStage instead (uni.subscribeAsCompletionStage(), mono.toFuture()).
  • Quarkus: two cases that silently lost the protection are build errors. A Quarkus REST resource that is an annotated class (R4C-QK-007: Quarkus REST creates resource classes itself, so the generated subclass never served the endpoints), and a protected bean with @LookupIfProperty/@LookupUnlessProperty (R4C-QK-006: the condition was ignored).

Fixed

  • Quarkus: build-time conditions on annotated classes. @IfBuildProfile, @UnlessBuildProfile, @IfBuildProperty and @UnlessBuildProperty on an annotated class were ignored, so both a production and a test implementation were beans (an ambiguous injection). The generated subclass of a disabled class is now disabled too.
  • Annotations repeated on generated subclasses. Overrides no longer repeat Bean Validation annotations (@Valid and constraints, which Bean Validation inherits from the overridden method): a @Valid return value stopped a Quarkus application from starting (HV000131). In Quarkus, a class annotation @Named without a value now names the bean after the annotated class, not …$Resilient, and a @Scheduled method no longer gives the bean two scopes. In Micronaut, a class marked @ResilientBean that also had policy annotations of its own applied its interface's policies twice (a @Retry(maxAttempts = 3) made 9 attempts); its generated subclass is no longer wrapped again.
  • Micronaut: subclasses and @MockBeans of annotated classes. The generated subclass carried @Replaces(C.class), which Micronaut applies to every bean assignable to C: an annotated subclass of C (with its own @Named) disappeared, and a test's @MockBean of C left no bean at all. The generated subclass no longer has it; the integration leaves out only C's own bean definition, and an unqualified @Inject C still prefers C$Resilient over subclass beans, as Micronaut prefers the exact type. A @MockBean stub of an annotated class is no longer reported as unprotected (R4C-MN-004). A generated subclass compiled without -Ar4c.inject=micronaut that Micronaut creates anyway (its constructor repeats @Inject) is reported with R4C-MN-004, since it lacks the class's scope.
  • Spring Boot. The actuator endpoint failed to start in web applications (Failed to extract parameter names), because the module was compiled without -parameters. An annotated class with a constructor without parameters and others, none @Autowired, failed to start (No default constructor found): Spring now uses the generated mirror of the no-argument constructor, as it uses that constructor on the class. Beans of a generic annotated interface (NameLookup implements Lookup<String>) can again be injected by their type argument (@Autowired Lookup<String>) once wrapped.
  • Test doubles of protected beans. Spring: a @MockitoSpyBean of an annotated interface was wrapped after Spring created it, so verify(spy) failed with NotAMockException; spies are no longer wrapped, as @MockitoBean mocks are not. Quarkus: @InjectMock on an annotated interface failed because its wrapper was a @Singleton; the wrapper of an @ApplicationScoped bean is now @ApplicationScoped too (behind a client proxy). Micronaut: a @MockBean of a @ResilientBean interface (a @Client) failed the startup with R4C-MN-003; it is now injected as it is.
  • Configuration and messages. down-when-open (Spring, Micronaut and Quarkus) split a derived id such as com.acme.Charges#charge(java.lang.String,int) at its comma, so it matched no breaker; parts are now joined back. Micronaut with eager singletons failed the startup with R4C-CFG-013 for an instance key of a @Prototype, @Refreshable or request-scoped protected bean; the default stays warn when such beans exist. An invalid resilience4cata.micronaut.verify-instances value now fails with the new code R4C-MN-006. Spring's startup report for R4C-CFG-013 points at resilience4cata.spring.verify-instances=warn for beans created after startup (@Lazy, prototypes, pipelines). The R4C-CFG-013 message ends its sentence before the known instances, and says <interface or class>. Links in messages and metadata use the site's current URLs.
  • Quarkus: generic annotated interfaces. @Inject Store<String> of an annotated Store<T> got the unprotected bean, because the wrapper's bean types were raw; they now keep the bean's type arguments.
  • Annotated classes keep their parameter names. The overrides of a generated subclass named their parameters p0, p1, …, so features that read names on the invoked method broke on annotated classes: Spring's @Cacheable(key = "#sku") and @EventListener(condition = "#event.urgent"), @PathVariable/@QueryValue without an explicit name in Spring MVC and Micronaut controllers, and Bean Validation, which reported a violation twice (p0 and the real name). They now keep the declared names; only a parameter named java, dev, the first part of a static fallback's package, or starting with r4c$ is renamed. A method inherited from a class file has its names only if that class was compiled with -parameters, as Spring Boot, Micronaut and Quarkus builds do. Interface wrappers are unchanged.

[0.8.0] - 2026-10-27 :id=0.8.0

Added

  • resilience4cata-all: one dependency to start with. It depends on core, annotations and config and has no code of its own, so a project needs two lines: resilience4cata-all and the processor on the annotation processor path (no dependency can bring a processor there). It is also a JPMS module, dev.resilience4cata.all, that reads the three transitively. The individual modules and the BOM work as before; use them to keep the annotations compile-only. In the BOM. See The short way.

[0.7.0] - 2026-10-27 :id=0.7.0

Added

  • locked = true on @Retry, @Hedge, @CircuitBreaker, @RateLimiter, @AdaptiveLimit, @Timeout and @Bulkhead (and Pipeline.Builder.locked()): the values the annotation sets win over per-instance overrides (instances.<id>.*, retryInstance(...) and friends); attributes it leaves unset stay overridable. An ignored override is logged when the instance is created, listed by the new registry.lockedOverrides() (LockedOverride), and reported key by key by config.verifyInstances(registry) with the new code R4C-CFG-014, so the Spring, Micronaut and Quarkus integrations report it at startup. Generated code changes only for annotations with locked = true. See Locking an instance.
  • The build checks the public API of every published module against the last release (./gradlew apiCompatibility, part of check), with japicmp.

[0.6.0] - 2026-10-26 :id=0.6.0

Added

  • @AdaptiveLimit: adaptive concurrency limits, sync and async, on interfaces, annotated classes and Pipeline.adaptiveLimit(...). Like a bulkhead, it caps the calls running at the same time, but it moves the cap by itself, between minLimit and maxLimit, from the latency of the calls: GRADIENT2 (the default) compares each window's average latency with a long-term average, AIMD grows by one and backs off by backoffRatio after a drop; both follow Netflix's concurrency-limits. A failure in dropOn (by default a CallTimeoutException, a BulkheadFullException or a TimeoutException from the policies inside) counts as a drop; other failures are not samples. A full limit rejects at once with the new LimitExceededException. The limit is recomputed once per window (window, at least minWindowSamples calls) by the thread that completes a call after the window ends: no background thread, no allocation on the success path. It sits between the rate limiter and the timeout: Fallback → Retry | Hedge → CircuitBreaker → RateLimiter → AdaptiveLimit → Timeout → Bulkhead. New API: AdaptiveLimitConfig (with the algorithms' tuning: smoothing, rttTolerance, longWindow, queueSize, backoffRatio), AdaptiveLimitHandle from registry.adaptiveLimit(id), AdaptiveLimitSnapshot in registry.snapshot().adaptiveLimits(), AdaptiveLimitEvent.Rejected and LimitChanged, the registry builder's adaptiveLimitDefaults, adaptiveLimit, adaptiveLimitOverlay and adaptiveLimitInstance, external configuration keys *.adaptiveLimit.*, and metrics r4c.adaptivelimit.limit, r4c.adaptivelimit.concurrent.calls, r4c.adaptivelimit.limit.changes and r4c.calls.rejected{kind=adaptivelimit} in both metrics modules. New processor error R4C-ATR-009. See Adaptive limit.
  • @Hedge stops starting attempts when one is rejected by an adaptive limit, as for an open breaker or a full bulkhead.
  • Quarkus Dev UI card: in dev mode, /q/dev-ui shows every policy instance of the registry (breakers, bulkheads, rate limiters, adaptive limits, retries and hedges with their budgets, timeouts), refreshed every two seconds, and can force a circuit breaker open, disable or reset it. Dev mode only: the card and its JsonRPC service do not exist in test or production builds. See Quarkus, Dev UI.

Fixed

  • The Spring Boot resilience4cata actuator endpoint now includes hedges when it reports a single instance (/actuator/resilience4cata/{instance}).

[0.5.0] - 2026-10-26 :id=0.5.0

Added

  • resilience4cata-micronaut: Micronaut 5 integration (Micronaut 5.2 or newer, Java 25, no AOP). Beans of annotated interfaces marked @ResilientBean(I.class) are replaced by their generated wrapper when Micronaut creates them (exposed as I only; Micronaut's AOP on the bean runs inside the policies); annotated classes are created by Micronaut as their generated subclass with the new processor option below, or returned from a @Factory; a @Context ResilienceConfig and ResilienceRegistry configured from the application's resilience4cata.* configuration in Micronaut's precedence (YAML lists, quoted derived instance ids, the file of every bad key), with BeanCreatedEventListener<ResilienceRegistry.Builder> customization and EventListener/Ticker beans; Micrometer metrics when resilience4cata-micrometer and a MeterRegistry are present; a resilience4cata health indicator (UP with open breakers unless listed); a read-only, sensitive resilience4cata management endpoint. New error area R4C-MN-001…005: misused @ResilientBean stops the startup; R4C-MN-004 reports an annotated class created without its generated subclass; R4C-MN-005 stops a retried or hedged @Transactional method of an annotated class (every attempt would share one transaction), unless resilience4cata.micronaut.allow-retry-inside-transaction=true. The module is compiled with --release 25, the one exception to the Java 21 baseline. See Micronaut.
  • Processor option -Ar4c.inject=micronaut: the generated subclass of an annotated class repeats the class's type-level annotations (scope, qualifiers, @Requires…) and adds @Replaces(C.class), so that Micronaut creates it in place of the class. Without the option the generated code is unchanged. A value other than micronaut, or the option without Micronaut on the compile class path, is the new error R4C-APT-006.
  • resilience4cata-quarkus: Quarkus extension (Quarkus 3.40 or newer; runtime resilience4cata-quarkus and deployment resilience4cata-quarkus-deployment, both in the BOM; no interceptors). At build time, an annotated class is vetoed and its generated subclass becomes the bean with the class's scope, qualifiers and stereotypes (calls on this intercepted); a bean of an annotated interface is injected through a synthetic wrapper bean (Resilient.of, an enabled alternative with the bean's qualifiers; @Inject of the class still gets the bean); a REST client interface (@RegisterRestClient) with a wrapper gets a @Default bean around the @RestClient one. A ResilienceRegistry bean (@DefaultBean) is configured from the application's resilience4cata.* keys through SmallRye Config (Quarkus profiles, expressions, R4C_ variables, file and line of every bad key), created at startup so configuration errors stop it; ResilienceRegistryCustomizer, EventListener and Ticker beans are applied; resilience4cata.instances.<id> keys are verified at startup (quarkus.resilience4cata.verify-instances, warn by default). Micrometer metrics with quarkus-micrometer and resilience4cata-micrometer (else OpenTelemetry), a resilience4cata readiness check (UP with open breakers unless listed in quarkus.resilience4cata.health.down-when-open). New error area R4C-QK-001…005, reported while Quarkus builds the application: R4C-QK-001 (MicroProfile/SmallRye Fault Tolerance and resilience4cata annotations on one method), R4C-QK-002 (a retried or hedged @Transactional method of an annotated class; quarkus.resilience4cata.allow-retry-inside-transaction), R4C-QK-003 (several unrelated annotated interfaces), R4C-QK-004 (producers on an annotated class), R4C-QK-005 (a normal-scoped annotated class without a constructor without parameters). See Quarkus, including SmallRye Fault Tolerance vs resilience4cata. A native image of the guide's application was built by hand (GraalVM for JDK 25.0.4, Quarkus 3.40.1, macOS arm64) and served the annotated class, the interface wrapper, the REST client wrapper, the readiness check and the metrics with no extra native configuration; native builds are not part of CI. In a native image, configuration errors name the source SmallRye reports there (Runtime Values for values fixed at build time) instead of the file and line.

Fixed

  • On a manual ticker (ManualClock), an async retry whose next delay is zero (delay = 0, or a FULL jitter that draws zero) started the next attempt on a new virtual thread instead of on the clock, so tests raced it. Every delay a policy schedules on a ticker now goes through Ticker.schedule, zero included, and runs on the thread that next advances the clock (advance(Duration.ZERO) included), as hedges after a failure already did. Without a ticker nothing changes. Ticker.schedule may now be called with a delay of zero.
  • R4C-RT-003 in a GraalVM native image now also points at the reachability metadata the processor writes (META-INF/native-image/dev.resilience4cata.generated/<class>/reachability-metadata.json), which must be on the image build's class path. The message is unchanged on the JVM.

[0.4.0] - 2026-10-25 :id=0.4.0

Added

  • @Hedge: hedged requests for asynchronous methods and Pipeline.executeAsync. The same call is started again when it has produced no result for delay (default 1 s), or at once when it fails with a failure in hedgeOn; the first success wins and the other attempts are cancelled through every inner policy (the breaker records nothing, the bulkhead frees the slot). maxAttempts (2 to 10, default 2), abortOn, and a hedge budget on by default (budgetPercent 10, budgetMinHedgesPerSecond 1, budgetWindow 10 s). Takes Retry's place in the order, and cannot be combined with @Retry. New HedgeConfig, HedgeCancelledException, HedgeEvent (Hedged, Won, Skipped, Failed), HedgeSnapshot in ResilienceSnapshot.hedges(), ResilienceRegistry.Builder.hedgeDefaults/hedge/hedgeOverlay/hedgeInstance, Pipeline.Builder.hedge(...), and config keys defaults.hedge.* (and configs.…, instances.…, environment variables). Only for idempotent calls. New errors R4C-HDG-001 (sync method), R4C-HDG-002 (with @Retry), R4C-ATR-008 (maxAttempts), R4C-RT-018 (Pipeline.execute with a hedge) and R4C-RT-019 (retry and hedge in one pipeline); R4C-ATR-007 now also covers the hedge budget. See Hedge.
  • resilience4cata-spring: Spring Boot 4 integration (Spring Boot 4.1 or newer, one module, no AOP). Annotated classes are instantiated as their generated subclass in place of the class (bean type, name, qualifiers and constructor injection unchanged; calls on this intercepted); beans of annotated interfaces are wrapped with Resilient.of after every other post-processor; a ResilienceRegistry bean (@ConditionalOnMissingBean) is configured from the application's resilience4cata.* properties in Spring's precedence, with […] instance ids and YAML lists, validated at startup with the file and line of every bad key; ResilienceRegistryCustomizer, EventListener and Ticker beans are applied; Micrometer metrics when resilience4cata-micrometer is present; a resilience4cata health indicator (UP with open breakers unless listed); a read-only resilience4cata actuator endpoint; IDE metadata; failure analyses; works with Spring AOT. New error area R4C-SPR-001…004; R4C-SPR-004 stops the startup when a retried or hedged method of an annotated class is also @Transactional (every attempt would share one transaction), unless resilience4cata.spring.allow-retry-inside-transaction=true. See Spring Boot.
  • resilience4cata-config: ResilienceConfig.Builder.precedenceByOrder() (sources rank by the order they are added, for frameworks with their own precedence) and properties(description, map, origins) (where each key was set, for messages).
  • GraalVM native image without configuration. The processor writes META-INF/native-image/dev.resilience4cata.generated/<generated class>/reachability-metadata.json for every generated class (one file per class, so the processor stays isolating), registering its public constructors for Resilient.of and Resilient.create, conditional on the annotated interface (or on Resilient for annotated classes). core ships a native-image.properties that initializes the holders of its background threads at run time. Needs GraalVM for JDK 23 or later; see Native image.
  • Annotated classes: the generated constructors keep the parameter names of the class's constructors and copy the constructor and parameter annotations (retention CLASS or RUNTIME), so dependency injection containers see @Inject/@Autowired, @Qualifier, @Named, @Value… on C$Resilient too. @ConstructorProperties gets the registry's name first. The generated overrides repeat the annotations of the methods they override and of their parameters (except resilience4cata's own annotations and bundles), so annotations read on the invoked method only, such as Jakarta's @Transactional under Spring or interceptor bindings, are no longer lost. See Constructors and dependency injection.

Changed

  • ResilienceEvent permits a new sealed subtype, HedgeEvent: a switch over ResilienceEvent without a default branch no longer compiles until it handles HedgeEvent (or adds default). ResilienceSnapshot has a new component, hedges(); its five-list constructor is kept. From now on the hierarchy grows in minor versions: keep a default branch, or listen to specific record types (Observability).
  • The subclass generated for an annotated class is no longer final (public class C$Resilient extends C), so frameworks can proxy it (CGLIB for @Transactional, scoped proxies). Interface wrappers stay final.
  • The generated constructors of annotated classes name their parameters after the class's constructor (registry, warehouse instead of registry, p0); a parameter named registry makes the registry parameter r4c$registry, and parameters named java or dev become r4c$p<index>. Calls are unaffected.

Fixed

  • Cancelling an async call now cancels the work inside it. Cancelling the future returned by a generated async method or by Pipeline.executeAsync (or completing it exceptionally with a CancellationException) used to stop at the outermost policy: the stage inside kept running, a bulkhead slot stayed taken until that stage ended, a retry could start another attempt, a fallback could run, and the circuit breaker could record the cancellation as a failure. The cancel now goes down through every layer to the method's stage: the retry cancels its backoff and starts no further attempt, the timeout cancels its deadline, the bulkhead frees its slot as soon as the stage ends (at once for a CompletableFuture) and a queued call leaves the queue, the breaker records nothing (a half-open permit is given back), the fallback does not run, and a call still waiting for a rate-limiter permit gives the permit back and never starts. No event is published for a cancel. A timeout that expires cancels the inner layers the same way, so a bulkhead inside a timeout frees its slot when the call times out. Each policy returns the same number of objects per call as before. See Async methods.

[0.3.0] - 2026-10-25 :id=0.3.0

Added

  • resilience4cata-opentelemetry: OpenTelemetry metrics. OpenTelemetryMetrics.create(openTelemetry), register(builder) and bindTo(registry) report rejections, retries, give-ups, breaker transitions, timeouts and fallbacks as counters fed by events, and breaker, bulkhead and rate-limiter state as asynchronous gauges read from the snapshot at each collection, so instances created later are included. Same metric names as the Micrometer module. Metrics only, no tracing yet. See OpenTelemetry.
  • resilience4cata-junit: a JUnit Jupiter extension. @ResilienceTest injects a fresh ManualClock and a ResilienceRegistry that follows it into each test (and its @BeforeEach/@AfterEach methods), with seed, startNanos and an opt-in failOnPendingTasks; @ConfigureRegistry methods customize the builder. Needs JUnit 5.10 or newer, JUnit 6 included. See Testing.
  • An async @Timeout call that finishes in time now takes its deadline off a ManualClock at once, so pendingTasks() no longer counts it until the deadline passes.
  • resilience4cata-micrometer: Micrometer metrics in three lines (MicrometerMetrics.create(meterRegistry), register(builder), bindTo(registry)): counters from events (rejections, breaker transitions, retries, give-ups, timeouts, fallbacks) and gauges for every circuit breaker, bulkhead and rate limiter, including instances created after binding. Replaces the hand-written recipe of the observability guide.
  • ResilienceRegistry.onInstanceCreated(Runnable): runs an action once per new policy instance (when a service is wrapped or a pipeline is built, never during a call), so that metrics bridges can register meters for instances created later (Observability).
  • Retry budget for @Retry, RetryConfig and Pipeline: budgetPercent, budgetMinRetriesPerSecond and budgetWindow cap the retries of one retry instance, shared by all its callers, at a reserve per second plus a percentage of its calls in the window, so that an outage does not multiply the load on the failing dependency. budgetPercent turns it on, with a default reserve of 10 retries per second. Off by default; without it, generated code and the success path are unchanged. A refused retry publishes RetryEvent.GaveUp with the new reason BUDGET_EXHAUSTED; RetrySnapshot.budget() shows the window. Config keys defaults.retry.budget… (and configs.…, instances.…, environment variables); new processor error R4C-ATR-007. Retry budget
  • Error codes for the test modules: R4C-TK-001…003 (ManualClock, ResilienceTesting) and R4C-JU-001…003 (the JUnit extension), each documented in the error reference with its fix.
  • The Micrometer and OpenTelemetry modules give every metric the same description (Prometheus # HELP text), checked by a test.

[0.2.0] - 2026-10-25 :id=0.2.0

Fixes three behaviour bugs of 0.1.0; one of the fixes changes a default (see Upgrading).

Upgrading from 0.1.0

  • Keep core and processor on the same version (import the BOM). Code generated by the 0.2.0 processor needs the 0.2.0 core; code generated by 0.1.0 still links against the 0.2.0 core, which only adds API.
  • abortOn no longer contains CallNotPermittedException by default, because an open circuit breaker now stops the retries on its own. Remove CallNotPermittedException from your own abortOn lists if you added it only for that; it does no harm if you keep it.
  • A call rejected by an open breaker can no longer be retried by any configuration, classifier included.

Changed

  • An open circuit breaker always stops the retries. A call rejected with CallNotPermittedException is never retried, whatever abortOn, retryOn or a classifier say, in generated code (sync and async) and in Pipeline. In 0.1.0 this came from the default abortOn, so setting your own abortOn silently dropped it and the retry kept calling an open breaker. RetryConfig.DEFAULTS.abortOn() (and defaults.retry.abortOn) is now empty; Retry.

Fixed

  • FallbackEvent.Applied is no longer published for a failure that no sibling fallback method takes (for example an Error when the only sibling takes Exception): such a failure is rethrown, and the event now means that a fallback really ran.
  • Error messages R4C-ATR-001 and R4C-CFG-002 state the duration grammar once instead of twice, and R4C-CFG-007 ends its sentence before "Known configs".
  • The processor's module descriptor declares AnnotationClaimer too, so -Xlint:processing stays quiet when the processor runs from the processor module path next to other processors.
  • A classifier that throws inside @CircuitBreaker (or a Pipeline's breaker) now ends the call at once, sync and async: the retry around it no longer classifies the classifier's exception as the method's failure and retries it (which could open the breaker and surface CallNotPermittedException instead). The breaker records one failure, the retry publishes GaveUp(CLASSIFIER_FAILED) once, and the fallback or the caller gets the classifier's exception. See A classifier that throws.

0.1.0 - 2026-10-25 :id=0.1.0

First release. Artifacts on Maven Central under the groupId dev.resilience4cata: resilience4cata-annotations, -core, -processor, -config, -testkit and -bom. Requires Java 21 or later; tested on Java 21 and 25.

Added

  • Six policies as annotations, applied in a fixed order (Fallback → Retry → CircuitBreaker → RateLimiter → Timeout → Bulkhead → method): @Retry (fixed or exponential backoff, jitter, time budget), @CircuitBreaker (count or time window, slow calls, half-open probes), @RateLimiter (GCRA, optional wait), @Timeout (on the caller's thread, interrupt-based), @Bulkhead (semaphore, optional wait) and @Fallback (sibling method or handler class).
  • Annotation processor that generates a plain Java class per annotated type: <Interface>$Resilient wrapping an implementation (Resilient.of), or <Class>$Resilient subclassing an annotated class (Resilient.create). Annotations and their values are checked at compile time; isolating incremental processing in Gradle; works with -Xlint:all -Werror.
  • Async methods returning CompletionStage<T> or CompletableFuture<T> (also ? extends T), with non-blocking forms of every policy (Async methods).
  • Composed annotations with @ResiliencePolicy, and type-level annotations.
  • Classifiers that decide from a result or an exception whether a call failed, and @Retry(onExhaustedResult = THROW).
  • Pipeline, a programmatic API with the same policies and order for code you cannot annotate.
  • ResilienceRegistry: defaults, named configs, per-instance overrides, instances shared by name, handles for circuit breakers (forceOpen, disable, reset), rate limiters and bulkheads.
  • Events and snapshots: listeners registered on the registry builder, read-only snapshots of every instance.
  • resilience4cata-config: external configuration from properties files, classpath resources, environment variables and system properties, with profiles, extends, per-instance overrides, strict validation and verifyInstances (keys).
  • resilience4cata-testkit: ManualClock and ResilienceTesting for deterministic tests of time-based behaviour.
  • resilience4cata-bom to align the versions of every module.
  • Error codes (R4C-…) on every compile-time and runtime error, each documented with a fix.
  • Allocation gate in CI: every sync success and rejection path allocates 0 bytes (Performance).

Known limitations

  • No Spring, CDI, Micrometer or OpenTelemetry integration modules; no YAML configuration.
  • The policy order cannot be changed, and configuration cannot be changed after the registry is built.
  • In the interface style, calls an implementation makes to itself are not intercepted.
  • Only CompletionStage<T> and CompletableFuture<T> are supported as async types (no ? super T, no reactive types).