Skip to content
resilience4cata
ENES

Resiliencia,
compilada.

Anota una interfaz Java con retry, circuit breaker, timeout y compañía. Al compilar, un procesador de anotaciones escribe el Java plano que los aplica. Sin proxies, sin reflexión, sin framework.

  • Java 21+
  • Funciona sin Spring, CDI ni AspectJ
  • Síncrono y CompletionStage
  • Apache 2.0
dependencies {
    implementation 'dev.resilience4cata:resilience4cata-all:0.10.0'
    annotationProcessor 'dev.resilience4cata:resilience4cata-processor:0.10.0'
}

Versión 0.10.0 en Maven Central. resilience4cata-all trae core, las anotaciones y la configuración; el procesador va solo en el path de procesadores de anotaciones. Guía de instalación

Tú escribesPaymentClient.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
El procesador escribePaymentClient$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;
    }
}
Cada llamada pasa por HttpPaymentClient.charge Abreviado: el archivo real usa nombres completamente calificados.

La protección es código que puedes leer

Resilience4j y MicroProfile envuelven tus llamadas en decoradores o proxies en tiempo de ejecución. resilience4cata hace ese trabajo en el compilador, así que lo que se ejecuta es Java común y corriente.

Entra con el debugger

El procesador escribe PaymentClient$Resilient junto a tus propias clases. Cada política son unas pocas líneas rectas en un método que puedes abrir, donde puedes poner un breakpoint y que lees en un stack trace.

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

Nada se asigna cuando las llamadas salen bien

Las llamadas síncronas que salen bien o se rechazan asignan cero bytes. Un gate de allocations ejecuta los benchmarks en cada pull request y hace fallar el build si eso cambia.

Los errores hacen fallar el build

Una duración mal escrita, un fallback con la firma equivocada o una anotación en un método final es un error de compilación con un código estable y una solución, no una sorpresa en producción.

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")
    ^

Cada código de error, con su solución

Medido, siempre

Cada número de este sitio viene de una corrida de JMH en el repositorio, contra Resilience4j con una configuración equivalente. Donde no ganamos, la tabla lo dice.

Máquina
AWS c7g.xlarge, 4 núcleos Graviton3
JMH
3 forks × 5 × 1 s, -prof gc
Comparado con
Resilience4j 2.4.0
Fecha
2026-10-24
Método, todos los resultados y notas de equidad
resilience4cata 0.1.0 Resilience4j 2.4.0
Retry, el primer intento sale bien1 thread
1.2 ns 22.9 ns
Circuit breaker, todas las llamadas salen bien4 threads, un breaker compartido
1.9 ns 832 ns
Timeout síncrono, la llamada termina a tiempo1 thread
22.2 ns 8 842 ns
Bulkhead con lugares libres1 thread · un empate
20.5 ns 20.4 ns
1 ns101001 00010 000

Tiempo por llamada, escala logarítmica. Menos es mejor.

Ver como tabla
EscenarioThreadsresilience4cataResilience4j
Retry, el primer intento sale bien11.2 ns, 0 B22.9 ns, 0 B
Circuit breaker, todas las llamadas salen bien41.9 ns, 0 B832 ns, 2 B
Timeout síncrono122.2 ns, 0 B8842 ns, 176 B
Bulkhead con lugares libres120.5 ns, 0 B20.4 ns, 0 B

De la dependencia a la primera llamada protegida

  1. Agrega el procesador

    Core en tiempo de ejecución, las anotaciones solo al compilar y el procesador en el path de procesadores de anotaciones.

    annotationProcessor 'dev.resilience4cata:
        resilience4cata-processor'
  2. Anota una interfaz

    Las duraciones son strings como "200ms", verificadas al compilar.

    @Retry(maxAttempts = 3)
    @Timeout("2s")
    Receipt charge(Order order);
  3. Envuelve tu implementación

    Un registry por aplicación comparte el estado de breakers y limiters por nombre.

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

Resiliencia basada en anotaciones para Java 21+ puro. Licencia Apache 2.0.

dev.resilience4cata:resilience4cata-bom:0.10.0