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. APipelinekeeps 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.Publisheror Reactive StreamsPublisher, so Reactor'sMonoandFlux; Mutiny'sUniandMulti; 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 aCompletionStageinstead (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,@IfBuildPropertyand@UnlessBuildPropertyon 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 (
@Validand constraints, which Bean Validation inherits from the overridden method): a@Validreturn value stopped a Quarkus application from starting (HV000131). In Quarkus, a class annotation@Namedwithout a value now names the bean after the annotated class, not…$Resilient, and a@Scheduledmethod no longer gives the bean two scopes. In Micronaut, a class marked@ResilientBeanthat 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 toC: an annotated subclass ofC(with its own@Named) disappeared, and a test's@MockBeanofCleft no bean at all. The generated subclass no longer has it; the integration leaves out onlyC's own bean definition, and an unqualified@Inject Cstill prefersC$Resilientover subclass beans, as Micronaut prefers the exact type. A@MockBeanstub of an annotated class is no longer reported as unprotected (R4C-MN-004). A generated subclass compiled without-Ar4c.inject=micronautthat 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
@MockitoSpyBeanof an annotated interface was wrapped after Spring created it, soverify(spy)failed withNotAMockException; spies are no longer wrapped, as@MockitoBeanmocks are not. Quarkus:@InjectMockon an annotated interface failed because its wrapper was a@Singleton; the wrapper of an@ApplicationScopedbean is now@ApplicationScopedtoo (behind a client proxy). Micronaut: a@MockBeanof a@ResilientBeaninterface (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 ascom.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,@Refreshableor request-scoped protected bean; the default stayswarnwhen such beans exist. An invalidresilience4cata.micronaut.verify-instancesvalue now fails with the new code R4C-MN-006. Spring's startup report for R4C-CFG-013 points atresilience4cata.spring.verify-instances=warnfor 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 annotatedStore<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/@QueryValuewithout an explicit name in Spring MVC and Micronaut controllers, and Bean Validation, which reported a violation twice (p0and the real name). They now keep the declared names; only a parameter namedjava,dev, the first part of a static fallback's package, or starting withr4c$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 oncore,annotationsandconfigand has no code of its own, so a project needs two lines:resilience4cata-alland 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 = trueon@Retry,@Hedge,@CircuitBreaker,@RateLimiter,@AdaptiveLimit,@Timeoutand@Bulkhead(andPipeline.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 newregistry.lockedOverrides()(LockedOverride), and reported key by key byconfig.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 withlocked = true. See Locking an instance.- The build checks the public API of every published module against the last release (
./gradlew apiCompatibility, part ofcheck), with japicmp.
[0.6.0] - 2026-10-26 :id=0.6.0
Added
@AdaptiveLimit: adaptive concurrency limits, sync and async, on interfaces, annotated classes andPipeline.adaptiveLimit(...). Like a bulkhead, it caps the calls running at the same time, but it moves the cap by itself, betweenminLimitandmaxLimit, from the latency of the calls:GRADIENT2(the default) compares each window's average latency with a long-term average,AIMDgrows by one and backs off bybackoffRatioafter a drop; both follow Netflix's concurrency-limits. A failure indropOn(by default aCallTimeoutException, aBulkheadFullExceptionor aTimeoutExceptionfrom the policies inside) counts as a drop; other failures are not samples. A full limit rejects at once with the newLimitExceededException. The limit is recomputed once per window (window, at leastminWindowSamplescalls) 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),AdaptiveLimitHandlefromregistry.adaptiveLimit(id),AdaptiveLimitSnapshotinregistry.snapshot().adaptiveLimits(),AdaptiveLimitEvent.RejectedandLimitChanged, the registry builder'sadaptiveLimitDefaults,adaptiveLimit,adaptiveLimitOverlayandadaptiveLimitInstance, external configuration keys*.adaptiveLimit.*, and metricsr4c.adaptivelimit.limit,r4c.adaptivelimit.concurrent.calls,r4c.adaptivelimit.limit.changesandr4c.calls.rejected{kind=adaptivelimit}in both metrics modules. New processor error R4C-ATR-009. See Adaptive limit.@Hedgestops 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-uishows 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
resilience4cataactuator 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 asIonly; 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@ContextResilienceConfigandResilienceRegistryconfigured from the application'sresilience4cata.*configuration in Micronaut's precedence (YAML lists, quoted derived instance ids, the file of every bad key), withBeanCreatedEventListener<ResilienceRegistry.Builder>customization andEventListener/Tickerbeans; Micrometer metrics whenresilience4cata-micrometerand aMeterRegistryare present; aresilience4catahealth indicator (UP with open breakers unless listed); a read-only, sensitiveresilience4catamanagement endpoint. New error areaR4C-MN-001…005: misused@ResilientBeanstops the startup;R4C-MN-004reports an annotated class created without its generated subclass;R4C-MN-005stops a retried or hedged@Transactionalmethod of an annotated class (every attempt would share one transaction), unlessresilience4cata.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 thanmicronaut, 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; runtimeresilience4cata-quarkusand deploymentresilience4cata-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 onthisintercepted); a bean of an annotated interface is injected through a synthetic wrapper bean (Resilient.of, an enabled alternative with the bean's qualifiers;@Injectof the class still gets the bean); a REST client interface (@RegisterRestClient) with a wrapper gets a@Defaultbean around the@RestClientone. AResilienceRegistrybean (@DefaultBean) is configured from the application'sresilience4cata.*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,EventListenerandTickerbeans are applied;resilience4cata.instances.<id>keys are verified at startup (quarkus.resilience4cata.verify-instances,warnby default). Micrometer metrics withquarkus-micrometerandresilience4cata-micrometer(else OpenTelemetry), aresilience4catareadiness check (UP with open breakers unless listed inquarkus.resilience4cata.health.down-when-open). New error areaR4C-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@Transactionalmethod 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 Valuesfor 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 aFULLjitter 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 throughTicker.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.schedulemay 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 andPipeline.executeAsync. The same call is started again when it has produced no result fordelay(default 1 s), or at once when it fails with a failure inhedgeOn; 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 (budgetPercent10,budgetMinHedgesPerSecond1,budgetWindow10 s). Takes Retry's place in the order, and cannot be combined with@Retry. NewHedgeConfig,HedgeCancelledException,HedgeEvent(Hedged,Won,Skipped,Failed),HedgeSnapshotinResilienceSnapshot.hedges(),ResilienceRegistry.Builder.hedgeDefaults/hedge/hedgeOverlay/hedgeInstance,Pipeline.Builder.hedge(...), and config keysdefaults.hedge.*(andconfigs.…,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.executewith 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 onthisintercepted); beans of annotated interfaces are wrapped withResilient.ofafter every other post-processor; aResilienceRegistrybean (@ConditionalOnMissingBean) is configured from the application'sresilience4cata.*properties in Spring's precedence, with[…]instance ids and YAML lists, validated at startup with the file and line of every bad key;ResilienceRegistryCustomizer,EventListenerandTickerbeans are applied; Micrometer metrics whenresilience4cata-micrometeris present; aresilience4catahealth indicator (UP with open breakers unless listed); a read-onlyresilience4cataactuator endpoint; IDE metadata; failure analyses; works with Spring AOT. New error areaR4C-SPR-001…004;R4C-SPR-004stops the startup when a retried or hedged method of an annotated class is also@Transactional(every attempt would share one transaction), unlessresilience4cata.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) andproperties(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.jsonfor every generated class (one file per class, so the processor stays isolating), registering its public constructors forResilient.ofandResilient.create, conditional on the annotated interface (or onResilientfor annotated classes).coreships anative-image.propertiesthat 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
CLASSorRUNTIME), so dependency injection containers see@Inject/@Autowired,@Qualifier,@Named,@Value… onC$Resilienttoo.@ConstructorPropertiesgets 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@Transactionalunder Spring or interceptor bindings, are no longer lost. See Constructors and dependency injection.
Changed
ResilienceEventpermits a new sealed subtype,HedgeEvent: aswitchoverResilienceEventwithout adefaultbranch no longer compiles until it handlesHedgeEvent(or addsdefault).ResilienceSnapshothas a new component,hedges(); its five-list constructor is kept. From now on the hierarchy grows in minor versions: keep adefaultbranch, 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 stayfinal. - The generated constructors of annotated classes name their parameters after the class's constructor (
registry, warehouseinstead ofregistry, p0); a parameter namedregistrymakes the registry parameterr4c$registry, and parameters namedjavaordevbecomer4c$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 aCancellationException) 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 aCompletableFuture) 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)andbindTo(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.@ResilienceTestinjects a freshManualClockand aResilienceRegistrythat follows it into each test (and its@BeforeEach/@AfterEachmethods), withseed,startNanosand an opt-infailOnPendingTasks;@ConfigureRegistrymethods customize the builder. Needs JUnit 5.10 or newer, JUnit 6 included. See Testing.- An async
@Timeoutcall that finishes in time now takes its deadline off aManualClockat once, sopendingTasks()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,RetryConfigandPipeline:budgetPercent,budgetMinRetriesPerSecondandbudgetWindowcap 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.budgetPercentturns 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 publishesRetryEvent.GaveUpwith the new reasonBUDGET_EXHAUSTED;RetrySnapshot.budget()shows the window. Config keysdefaults.retry.budget…(andconfigs.…,instances.…, environment variables); new processor error R4C-ATR-007. Retry budget - Error codes for the test modules:
R4C-TK-001…003(ManualClock,ResilienceTesting) andR4C-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
# HELPtext), 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
coreandprocessoron the same version (import the BOM). Code generated by the 0.2.0 processor needs the 0.2.0core; code generated by 0.1.0 still links against the 0.2.0core, which only adds API. abortOnno longer containsCallNotPermittedExceptionby default, because an open circuit breaker now stops the retries on its own. RemoveCallNotPermittedExceptionfrom your ownabortOnlists 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
CallNotPermittedExceptionis never retried, whateverabortOn,retryOnor a classifier say, in generated code (sync and async) and inPipeline. In 0.1.0 this came from the defaultabortOn, so setting your ownabortOnsilently dropped it and the retry kept calling an open breaker.RetryConfig.DEFAULTS.abortOn()(anddefaults.retry.abortOn) is now empty; Retry.
Fixed
FallbackEvent.Appliedis no longer published for a failure that no sibling fallback method takes (for example anErrorwhen the only sibling takesException): 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
AnnotationClaimertoo, so-Xlint:processingstays quiet when the processor runs from the processor module path next to other processors. - A classifier that throws inside
@CircuitBreaker(or aPipeline'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 surfaceCallNotPermittedExceptioninstead). The breaker records one failure, the retry publishesGaveUp(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>$Resilientwrapping an implementation (Resilient.of), or<Class>$Resilientsubclassing 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>orCompletableFuture<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 byname, 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 andverifyInstances(keys).resilience4cata-testkit:ManualClockandResilienceTestingfor deterministic tests of time-based behaviour.resilience4cata-bomto 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>andCompletableFuture<T>are supported as async types (no? super T, no reactive types).