Skip to content

0.4.0

Choose a tag to compare

@fabiangftech fabiangftech released this 27 Oct 13:48
· 67 commits to main since this release

Released 2026-10-25. On Maven Central as dev.resilience4cata:*:0.4.0 (installation, documentation).

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.