Repository navigation
0.4.0
Released 2026-10-25. On Maven Central as dev.resilience4cata:*:0.4.0 (installation, documentation).
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.