Skip to content
resilience4cata
ENES

Resilience,
compiled.

Annotate a Java interface with retry, circuit breaker, timeout and friends. At compile time, an annotation processor writes the plain Java that enforces them. No proxies, no reflection, no framework.

  • Java 21+
  • Works without Spring, CDI or AspectJ
  • Sync and CompletionStage
  • Apache 2.0
dependencies {
    implementation 'dev.resilience4cata:resilience4cata-all:0.10.0'
    annotationProcessor 'dev.resilience4cata:resilience4cata-processor:0.10.0'
}

Version 0.10.0 on Maven Central. resilience4cata-all brings core, the annotations and configuration; the processor only goes on the annotation processor path. Installation guide

You writePaymentClient.java
public interface PaymentClient {

    @Retry(maxAttempts = 3, delay = "200ms")
    @CircuitBreaker
    @Timeout("2s")
    Receipt charge(Order order);
}

// once, at startup
ResilienceRegistry registry =
        ResilienceRegistry.builder().build();
PaymentClient client = Resilient.of(
        PaymentClient.class, new HttpPaymentClient(), registry);

client.charge(order); // retried, guarded, timed out
The processor writesPaymentClient$Resilient.java
// Generated by the resilience4cata annotation processor. Do not edit.
public final class PaymentClient$Resilient implements PaymentClient {

    private final PaymentClient delegate;
    private final RetryPolicy retry0;
    private final CircuitBreakerPolicy circuitBreaker0;
    private final TimeoutPolicy timeout0;
    // … constructor: each policy resolved once from the registry

    @Override
    public Receipt charge(Order p0) {
        final RetryPolicy r4c$retry = this.retry0;
        final long r4c$start = r4c$retry.start();
        int r4c$attempts = 0;
        while (true) {
            try {
                return this.r4c$circuitBreaker0(p0);
            } catch (Throwable r4c$e) {
                final long r4c$delay = r4c$retry.onFailure(++r4c$attempts, r4c$e, r4c$start);
                if (r4c$delay < 0 || !r4c$retry.pause(r4c$delay)) {
                    throw r4c$e;
                }
            }
        }
    }
    private Receipt r4c$circuitBreaker0(Order p0) {
        final CircuitBreakerPolicy r4c$cb = this.circuitBreaker0;
        final long r4c$token = r4c$cb.acquire();
        if (r4c$token < 0) {
            throw r4c$cb.rejection();
        }
        final long r4c$started = r4c$cb.startTimer();
        try {
            final Receipt r4c$result = this.r4c$timeout0(p0);
            r4c$cb.onSuccess(r4c$token, r4c$started);
            return r4c$result;
        } catch (Throwable r4c$e) {
            r4c$cb.onFailure(r4c$token, r4c$started, r4c$e);
            throw r4c$e;
        }
    }
    private Receipt r4c$timeout0(Order p0) {
        final TimeoutPolicy r4c$timeout = this.timeout0;
        final TimeoutPolicy.Scope r4c$scope = r4c$timeout.enter();
        final Receipt r4c$result;
        try {
            r4c$result = this.delegate.charge(p0);
        } catch (Throwable r4c$e) {
            r4c$timeout.failed(r4c$scope, r4c$e);
            throw r4c$e;
        }
        r4c$timeout.succeeded(r4c$scope);
        return r4c$result;
    }
}
Each call passes through HttpPaymentClient.charge Abridged: the real file uses fully qualified names.

The protection is code you can read

Resilience4j and MicroProfile wrap your calls in decorators or proxies at runtime. resilience4cata does that work in the compiler, so what runs is ordinary Java.

Step into it with a debugger

The processor writes PaymentClient$Resilient next to your own classes. Every policy is a few straight lines in a method you can open, set a breakpoint in and read in a stack trace.

at HttpPaymentClient.charge
at PaymentClient$Resilient.r4c$timeout0
at PaymentClient$Resilient.r4c$circuitBreaker0
at PaymentClient$Resilient.charge
at CheckoutService.pay

Nothing allocated when calls succeed

Synchronous calls that succeed or are rejected allocate zero bytes. An allocation gate runs the benchmarks on every pull request and fails the build if that changes.

Mistakes fail the build

A bad duration, a fallback with the wrong signature or an annotation on a final method is a compile error with a stable code and a fix, not a surprise in production.

error: [R4C-ATR-001] @Retry.delay = "2 seconds"
  is not a valid duration: … Use a number
  followed by ns, us, ms, s, m, h or d …
    @Retry(delay = "2 seconds")
    ^

Every error code, with a fix

Measured, every time

Every number on this site comes from a JMH run in the repository, against Resilience4j with equivalent settings. Where we don’t win, the table says so.

Machine
AWS c7g.xlarge, 4 Graviton3 cores
JMH
3 forks × 5 × 1 s, -prof gc
Compared
Resilience4j 2.4.0
Date
2026-10-24
Methods, all results and fairness notes
resilience4cata 0.1.0 Resilience4j 2.4.0
Retry, first attempt succeeds1 thread
1.2 ns 22.9 ns
Circuit breaker, every call succeeds4 threads, one shared breaker
1.9 ns 832 ns
Sync timeout, call finishes in time1 thread
22.2 ns 8 842 ns
Bulkhead with free slots1 thread · a tie
20.5 ns 20.4 ns
1 ns101001 00010 000

Time per call, logarithmic scale. Lower is better.

Show as a table
ScenarioThreadsresilience4cataResilience4j
Retry, first attempt succeeds11.2 ns, 0 B22.9 ns, 0 B
Circuit breaker, every call succeeds41.9 ns, 0 B832 ns, 2 B
Sync timeout122.2 ns, 0 B8842 ns, 176 B
Bulkhead with free slots120.5 ns, 0 B20.4 ns, 0 B

From dependency to first protected call

  1. Add the processor

    Core at runtime, annotations compile-only, the processor on the annotation processor path.

    annotationProcessor 'dev.resilience4cata:
        resilience4cata-processor'
  2. Annotate an interface

    Durations are strings like "200ms", checked when you compile.

    @Retry(maxAttempts = 3)
    @Timeout("2s")
    Receipt charge(Order order);
  3. Wrap your implementation

    One registry per application shares breaker and limiter state by name.

    Resilient.of(PaymentClient.class,
        new HttpPaymentClient(), registry);
resilience4cata

Annotation-driven resilience for plain Java 21+. Apache License 2.0.

dev.resilience4cata:resilience4cata-bom:0.10.0