Skip to content
resilience4cata
ENES

Referencia de errores

Todo error que reporta resilience4cata empieza con un código estable entre corchetes, por ejemplo:

error: [R4C-FB-002] @Fallback(method = "findFallbak") on find: no method named findFallbak in UserClient. Did you mean findFallback? Expected a default or static method java.lang.String findFallbak(java.lang.String, <a Throwable type> e), where the exception parameter is optional.

El código indica qué sección de esta página explica el error. El resto del mensaje dice dónde (el compilador señala el elemento anotado; la clase, el método y el atributo aparecen en el texto), qué está mal y cómo corregirlo. Los mensajes están en inglés.

Prefijo Lo reporta Cuándo
R4C-APT procesador de anotaciones ubicación de las anotaciones, problemas generales
R4C-ATR procesador de anotaciones valores de atributos
R4C-CMP procesador de anotaciones anotaciones compuestas (bundles @ResiliencePolicy)
R4C-ASY procesador de anotaciones métodos asíncronos
R4C-HDG procesador de anotaciones @Hedge
R4C-CLS procesador de anotaciones clases classifier y onExhaustedResult
R4C-FB procesador de anotaciones @Fallback
R4C-SUB procesador de anotaciones clases anotadas (subclases generadas)
R4C-RT resilience4cata-core en ejecución Resilient.of, Resilient.create, Pipeline.Builder
R4C-REG resilience4cata-core en ejecución builders *Config, ResilienceRegistry y su builder: configuraciones con nombre, instancias
R4C-CFG resilience4cata-config en ejecución configuración externa (ResilienceConfig)
R4C-SPR resilience4cata-spring al arrancar la integración con Spring Boot: beans que no pueden protegerse como se declararon
R4C-MN resilience4cata-micronaut al arrancar o al crear un bean la integración con Micronaut: beans que no pueden protegerse como se declararon
R4C-QK resilience4cata-quarkus mientras Quarkus construye la aplicación la extensión de Quarkus: beans que no pueden protegerse como se declararon, o quedarían protegidos dos veces
R4C-TK resilience4cata-testkit en las pruebas ManualClock, ResilienceTesting
R4C-JU resilience4cata-junit en las pruebas la extensión de JUnit (@ResilienceTest)

Los códigos son estables: nunca se renumeran ni cambian de significado. Un error nuevo recibe el siguiente número libre de su área; un error que ya no se reporta conserva su código, marcado abajo como retirado.

El build compila los ejemplos Java, erróneo y corregido, de los códigos del procesador de anotaciones (ErrorCodeTest en el módulo processor): cada ejemplo erróneo reporta su código y cada corrección compila sin advertencias.

Procesador de anotaciones: ubicación (R4C-APT)

R4C-APT-001

Causa: una anotación de resiliencia en un enum o un record, o en uno de sus métodos. El estilo de envoltorio necesita una interfaz, y el estilo de subclase debe extender el tipo, pero los enums y los records son finales.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public record PriceClient(String baseUrl) {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

Corrección: declara los métodos en una interfaz, impleméntala con el record y envuelve la implementación con Resilient.of(PriceClient.class, implementation, registry).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry
    String price(String sku);
}

R4C-APT-002

Causa: una anotación de resiliencia en un método estático o privado. Las llamadas a esos métodos no se pueden interceptar con un envoltorio ni con una subclase.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry
    static String price(String sku) {
        return "1";
    }
}

Corrección: anota un método de instancia no privado (abstracto o default en una interfaz; sobrescribible en una clase).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry
    String price(String sku);
}

R4C-APT-003

Causa: la interfaz anotada es privada (un miembro privado de una clase). La clase generada <Interface>$Resilient es de primer nivel y debe implementarla.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public final class Clients {
    private interface PriceClient {
        @Retry
        String price(String sku);
    }
}

Corrección: haz la interfaz package-private o pública.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public final class Clients {
    interface PriceClient {
        @Retry
        String price(String sku);
    }
}

R4C-APT-004

Causa: una anotación tiene un atributo que el procesador no conoce, por ejemplo Unknown attribute @Retry.hedges. Pasa cuando resilience4cata-annotations es más nuevo que resilience4cata-processor.

Corrección: usa la misma versión para todos los artefactos de resilience4cata, idealmente mediante el BOM:

dependencies {
    implementation platform('dev.resilience4cata:resilience4cata-bom:<version>')
    compileOnly 'dev.resilience4cata:resilience4cata-annotations'
    implementation 'dev.resilience4cata:resilience4cata-core'
    annotationProcessor platform('dev.resilience4cata:resilience4cata-bom:<version>')
    annotationProcessor 'dev.resilience4cata:resilience4cata-processor'
}

R4C-APT-005

Causa: el procesador no pudo escribir un archivo fuente generado, o el archivo de metadatos de imagen nativa que escribe junto a él (META-INF/native-image/dev.resilience4cata.generated/<Interface>$Resilient/reachability-metadata.json en la salida de clases): falló el Filer del compilador, por ejemplo porque el directorio de salida no admite escritura, o porque otro procesador o un archivo fuente ya define <Interface>$Resilient.

Corrección: revisa el directorio de fuentes generadas del build (con Gradle, build/generated/sources/annotationProcessor), asegúrate de que ninguna clase escrita a mano se llame <Interface>$Resilient y recompila tras ./gradlew clean.

R4C-APT-006

Causa: la opción del compilador -Ar4c.inject tiene un valor distinto de micronaut; o es -Ar4c.inject=micronaut pero io.micronaut.context.annotation.Replaces de Micronaut no está en el class path de compilación, así que la subclase generada de una clase anotada no puede reemplazar a la clase como bean de Micronaut. La opción se lee una vez por compilación; el error no tiene posición en el código.

Incorrecto (compilado con -Ar4c.inject=spring):

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class InventoryService {
    @Retry
    public int stock(String sku) {
        return 1;
    }
}

Corrección: usa -Ar4c.inject=micronaut en una aplicación Micronaut, con io.micronaut:micronaut-inject en el class path de compilación (toda aplicación Micronaut lo tiene); quita la opción con cualquier otro contenedor (Spring Boot no necesita ninguna: consulta la guía de Spring Boot). Consulta la guía de Micronaut.

tasks.withType(JavaCompile).configureEach {
    options.compilerArgs.add('-Ar4c.inject=micronaut')
}

Procesador de anotaciones: valores de atributos (R4C-ATR)

R4C-ATR-001

Causa: un atributo de duración (delay, maxDelay, maxDuration, slowCallDuration, waitInOpen, maxWaitInHalfOpen, maxWait, period, budgetWindow, window, o el valor de @Timeout) no es una duración válida.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(delay = "2 seconds")
    String price(String sku);
}

Corrección: usa un número seguido de ns, us, ms, s, m, h o d ("250ms", "2s"), una duración ISO-8601 ("PT2S") o "0".

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(delay = "2s")
    String price(String sku);
}

R4C-ATR-002

Causa: una duración que debe ser mayor que cero vale "0": el valor de @Timeout o @RateLimiter.period.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Timeout;

public interface PriceClient {
    @Timeout("0")
    String price(String sku);
}

Corrección: pon una duración positiva. Para desactivar la política, usa enabled = "false" (o la configuración).

package com.acme;

import dev.resilience4cata.annotation.Timeout;

public interface PriceClient {
    @Timeout("2s")
    String price(String sku);
}

R4C-ATR-003

Causa: un conteo es menor que 1: maxAttempts, windowSize, minimumCalls, halfOpenCalls, maxConcurrent, permits, initialLimit, minLimit o maxLimit.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(maxAttempts = 0)
    String price(String sku);
}

Corrección: ponlo en 1 o más (maxAttempts cuenta la primera llamada), u omítelo para usar el valor configurado.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(maxAttempts = 3)
    String price(String sku);
}

R4C-ATR-004

Causa: @Retry.multiplier es menor que 1, infinito o NaN.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(multiplier = 0.5)
    String price(String sku);
}

Corrección: usa un número finito de al menos 1 (1 mantiene la espera constante).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(multiplier = 2.0)
    String price(String sku);
}

R4C-ATR-005

Causa: @CircuitBreaker.failureRateThreshold o slowCallRateThreshold no es un porcentaje mayor que 0 y como mucho 100.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.CircuitBreaker;

public interface PriceClient {
    @CircuitBreaker(failureRateThreshold = 150)
    String price(String sku);
}

Corrección: usa un valor en (0, 100].

package com.acme;

import dev.resilience4cata.annotation.CircuitBreaker;

public interface PriceClient {
    @CircuitBreaker(failureRateThreshold = 50)
    String price(String sku);
}

R4C-ATR-006

Causa: enabled no es "" (sin definir), "true" ni "false".

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(enabled = "yes")
    String price(String sku);
}

Corrección: usa "true" o "false", u omítelo.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(enabled = "false")
    String price(String sku);
}

R4C-ATR-007

Causa: un atributo del presupuesto de reintentos o de hedge está fuera de rango: budgetPercent es negativo, infinito o NaN, budgetMinRetriesPerSecond (@Retry) o budgetMinHedgesPerSecond (@Hedge) es negativo, o budgetWindow es menor que 1 s o mayor que 1 h. (Una budgetWindow que ni siquiera es una duración es R4C-ATR-001.)

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(budgetPercent = 20, budgetWindow = "500ms")
    String price(String sku);
}

Corrección: usa un porcentaje de 0 o más, una reserva de 0 o más reintentos por segundo y una ventana entre "1s" y "1h"; u omite el atributo para heredarlo (budgetWindow vale 10s por defecto).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(budgetPercent = 20, budgetMinRetriesPerSecond = 1, budgetWindow = "10s")
    String price(String sku);
}

R4C-ATR-008

Causa: @Hedge(maxAttempts = …) es menor que 2 o mayor que 10. maxAttempts cuenta todos los intentos, incluido el primero, así que 1 nunca lanzaría un hedge; más de 10 intentos en vuelo para una llamada no se admiten.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Hedge;
import java.util.concurrent.CompletableFuture;

public interface QuoteClient {
    @Hedge(maxAttempts = 1, delay = "50ms")
    CompletableFuture<String> quote(String sku);
}

Corrección: usa un valor de 2 (un hedge, el valor por defecto) a 10, o quita @Hedge si la llamada no debe cubrirse.

package com.acme;

import dev.resilience4cata.annotation.Hedge;
import java.util.concurrent.CompletableFuture;

public interface QuoteClient {
    @Hedge(maxAttempts = 2, delay = "50ms")
    CompletableFuture<String> quote(String sku);
}

R4C-ATR-009

Causa: los límites de @AdaptiveLimit están desordenados (minLimit por encima de maxLimit, o initialLimit fuera de ellos), o su window dura menos de 10 ms o más de 1 h. Solo se comparan los límites puestos en la anotación; uno que viene de una configuración con nombre o externa se revisa cuando el registry la resuelve (R4C-REG-003).

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.AdaptiveLimit;

public interface OrderClient {
    @AdaptiveLimit(minLimit = 50, maxLimit = 20)
    String place(String order);
}

Corrección: mantén minLimit <= initialLimit <= maxLimit, y una window de "10ms" a "1h".

package com.acme;

import dev.resilience4cata.annotation.AdaptiveLimit;

public interface OrderClient {
    @AdaptiveLimit(minLimit = 20, maxLimit = 50)
    String place(String order);
}

Procesador de anotaciones: anotaciones compuestas (R4C-CMP)

R4C-CMP-001

Causa: un tipo de anotación lleva una anotación de resiliencia pero no está marcado con @ResiliencePolicy, así que el procesador ignoraría sus políticas allí donde se use.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retry(maxAttempts = 5)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Patient {
}

Corrección: añade @ResiliencePolicy.

package com.acme;

import dev.resilience4cata.annotation.ResiliencePolicy;
import dev.resilience4cata.annotation.Retry;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@ResiliencePolicy
@Retry(maxAttempts = 5)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Patient {
}

R4C-CMP-002

Causa: dos anotaciones compuestas en el mismo elemento dan valores distintos al mismo atributo, y ninguna puede ganar.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.ResiliencePolicy;
import dev.resilience4cata.annotation.Retry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public interface PriceClient {
    @Fast
    @Patient
    String price(String sku);
}

@ResiliencePolicy
@Retry(maxAttempts = 2)
@Retention(RetentionPolicy.CLASS)
@interface Fast {
}

@ResiliencePolicy
@Retry(maxAttempts = 5)
@Retention(RetentionPolicy.CLASS)
@interface Patient {
}

Corrección: deja solo uno de los bundles, haz que coincidan, o define el atributo directamente en el elemento, que gana sobre todos los bundles.

package com.acme;

import dev.resilience4cata.annotation.ResiliencePolicy;
import dev.resilience4cata.annotation.Retry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public interface PriceClient {
    @Patient
    String price(String sku);
}

@ResiliencePolicy
@Retry(maxAttempts = 2)
@Retention(RetentionPolicy.CLASS)
@interface Fast {
}

@ResiliencePolicy
@Retry(maxAttempts = 5)
@Retention(RetentionPolicy.CLASS)
@interface Patient {
}

Procesador de anotaciones: métodos asíncronos (R4C-ASY)

R4C-ASY-001

Causa: un método anotado devuelve un tipo CompletionStage no soportado: un tipo raw, un argumento ? super X, o un subtipo de CompletionStage distinto de CompletableFuture.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;
import java.util.concurrent.CompletionStage;

public interface PriceClient {
    @Retry
    CompletionStage<? super Integer> price(String sku);
}

Corrección: devuelve CompletionStage<T> o CompletableFuture<T>, donde T también puede ser ? o ? extends X.

package com.acme;

import dev.resilience4cata.annotation.Retry;
import java.util.concurrent.CompletionStage;

public interface PriceClient {
    @Retry
    CompletionStage<Integer> price(String sku);
}

R4C-ASY-002

Causa: un método anotado devuelve un tipo reactivo perezoso: un Flow.Publisher o un Publisher de Reactive Streams (Mono y Flux de Reactor, Flowable de RxJava), Uni o Multi de Mutiny, o Single, Maybe, Completable u Observable de RxJava. Un método así solo construye un pipeline que se ejecuta después, al suscribirse, así que las políticas protegerían su construcción, no el trabajo: un fallo nunca se reintentaría ni tendría timeout.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;
import java.util.concurrent.Flow;

public interface PriceClient {
    @Retry
    Flow.Publisher<Integer> price(String sku);
}

Corrección: devuelve CompletionStage<T> o CompletableFuture<T>, convirtiendo en la implementación (uni.subscribeAsCompletionStage(), mono.toFuture()), o mantén el tipo reactivo y usa los operadores de retry y timeout de la propia librería reactiva.

package com.acme;

import dev.resilience4cata.annotation.Retry;
import java.util.concurrent.CompletionStage;

public interface PriceClient {
    @Retry
    CompletionStage<Integer> price(String sku);
}

Procesador de anotaciones: hedges (R4C-HDG)

R4C-HDG-001

Causa: @Hedge se aplica a un método sincrónico: uno que no devuelve CompletionStage<T> ni CompletableFuture<T>. Un hedge vuelve a lanzar la llamada mientras el primer intento sigue en curso, y eso necesita una llamada asíncrona. Un @Hedge en el tipo, o en un bundle @ResiliencePolicy, se aplica a todos los métodos, así que se informa cada método sincrónico del tipo.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Hedge;

public interface QuoteClient {
    @Hedge(delay = "50ms")
    String quote(String sku);
}

Corrección: devuelve un CompletableFuture (o un CompletionStage), o quita @Hedge del método. Para un @Hedge en el tipo, anota los métodos async en lugar del tipo.

package com.acme;

import dev.resilience4cata.annotation.Hedge;
import java.util.concurrent.CompletableFuture;

public interface QuoteClient {
    @Hedge(delay = "50ms")
    CompletableFuture<String> quote(String sku);
}

R4C-HDG-002

Causa: @Hedge y @Retry se aplican al mismo método, directamente, en el tipo o a través de bundles @ResiliencePolicy. Ocupan el mismo lugar en el orden de las políticas, y un retry alrededor de un hedge multiplicaría los intentos.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Hedge;
import dev.resilience4cata.annotation.Retry;
import java.util.concurrent.CompletableFuture;

public interface QuoteClient {
    @Hedge(delay = "50ms")
    @Retry(maxAttempts = 3)
    CompletableFuture<String> quote(String sku);
}

Corrección: quédate con uno. Un hedge ya lanza un intento nuevo enseguida cuando uno falla con una falla de hedgeOn, así que maxAttempts en el hedge cubre el reintento simple.

package com.acme;

import dev.resilience4cata.annotation.Hedge;
import java.util.concurrent.CompletableFuture;

public interface QuoteClient {
    @Hedge(delay = "50ms", maxAttempts = 3)
    CompletableFuture<String> quote(String sku);
}

Procesador de anotaciones: orden de las políticas (R4C-ORD)

R4C-ORD-001

Causa: @PolicyOrder nombra una capa más de una vez (en el método, en su tipo o en un bundle). Cada capa tiene un solo lugar en el orden.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Layer;
import dev.resilience4cata.annotation.PolicyOrder;
import dev.resilience4cata.annotation.Retry;
import dev.resilience4cata.annotation.Timeout;

public interface ReportClient {
    @Retry
    @Timeout("2s")
    @PolicyOrder({Layer.TIMEOUT, Layer.RETRY, Layer.TIMEOUT})
    String report(String id);
}

Corrección: nombra cada capa una sola vez, de la más exterior a la más interior.

package com.acme;

import dev.resilience4cata.annotation.Layer;
import dev.resilience4cata.annotation.PolicyOrder;
import dev.resilience4cata.annotation.Retry;
import dev.resilience4cata.annotation.Timeout;

public interface ReportClient {
    @Retry
    @Timeout("2s")
    @PolicyOrder({Layer.TIMEOUT, Layer.RETRY})
    String report(String id);
}

Procesador de anotaciones: clasificadores (R4C-CLS)

La clase generada crea una instancia de cada clase classifier por método, así que la clase debe ser concreta, no genérica, con un constructor sin argumentos accesible, e implementar Classifier<T> para el tipo de resultado del método.

R4C-CLS-001

Causa: classifier está definido, pero dev.resilience4cata.Classifier no está en el classpath de compilación.

Incorrecto: compilar esto sin resilience4cata-core en el classpath:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: añade la dependencia de core:

dependencies {
    implementation 'dev.resilience4cata:resilience4cata-core:<version>'
}

R4C-CLS-002

Causa: classifier nombra una interfaz, una anotación o un enum en lugar de una clase.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Classifier.class)
    String price(String sku);
}

Corrección: nombra una clase que implemente Classifier<T>.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-003

Causa: la clase del clasificador es abstracta, así que no se puede instanciar.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    abstract class Retryable implements Classifier<String> {
    }
}

Corrección: usa una clase concreta.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-004

Causa: la clase del clasificador declara parámetros de tipo; el código generado solo podría crearla con un tipo raw no comprobado.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable<T> implements Classifier<T> {
        @Override
        public Outcome classify(T result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: implementa Classifier<T> con un T concreto (o un supertipo, como Classifier<Object>).

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<Object> {
        @Override
        public Outcome classify(Object result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-005

Causa: la clase del clasificador (o una clase que la contiene) es privada, o package-private en otro paquete, así que la clase generada, en el paquete de la interfaz, no puede crearla.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public final class Clients {
    interface PriceClient {
        @Retry(classifier = Retryable.class)
        String price(String sku);
    }

    private static final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: hazla pública, o package-private en el paquete de la interfaz.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public final class Clients {
    interface PriceClient {
        @Retry(classifier = Retryable.class)
        String price(String sku);
    }

    static final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-006

Causa: el clasificador es una clase interna (anidada no estática), que necesita una instancia de su clase contenedora.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public final class Clients {
    interface PriceClient {
        @Retry(classifier = Retryable.class)
        String price(String sku);
    }

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: hazla static.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public final class Clients {
    interface PriceClient {
        @Retry(classifier = Retryable.class)
        String price(String sku);
    }

    static final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-007

Causa: el clasificador no tiene un constructor sin argumentos que la clase generada pueda llamar.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        private Retryable() {
        }

        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: añade un constructor sin argumentos público (o package-private, en el paquete de la interfaz), o quita el privado.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-008

Causa: la clase del clasificador no implementa dev.resilience4cata.Classifier.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = String.class)
    String price(String sku);
}

Corrección: nombra una clase que implemente Classifier<T>.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-009

Causa: el clasificador implementa el tipo raw Classifier, así que no se puede comprobar su tipo de resultado.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier {
        @Override
        public Outcome classify(Object result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: implementa Classifier<T> con el tipo de resultado del método, como sugiere el mensaje.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-010

Causa: el clasificador implementa Classifier<X>, pero el resultado del método (el valor del stage, en un método asíncrono) no es asignable a X.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<Integer> {
        @Override
        public Outcome classify(Integer result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: implementa Classifier para el tipo de resultado del método o un supertipo suyo.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku);

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

R4C-CLS-011

Causa: @Retry define a la vez classifier y retryOn, o @CircuitBreaker define a la vez classifier y recordOn. El clasificador decide qué excepciones son fallos, así que la lista se ignoraría.

Incorrecto:

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;
import java.io.IOException;

public interface PriceClient {
    @Retry(classifier = Retryable.class, retryOn = IOException.class)
    String price(String sku) throws IOException;

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure == null ? Outcome.SUCCESS : Outcome.FAILURE;
        }
    }
}

Corrección: quita retryOn (o recordOn) y devuelve Outcome.FAILURE para esas excepciones desde el clasificador. abortOn e ignoreOn siguen aplicándose antes del clasificador.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.Retry;
import java.io.IOException;

public interface PriceClient {
    @Retry(classifier = Retryable.class)
    String price(String sku) throws IOException;

    final class Retryable implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure instanceof IOException ? Outcome.FAILURE : Outcome.SUCCESS;
        }
    }
}

R4C-CLS-012

Causa: @Retry define onExhaustedResult sin classifier. Solo un clasificador puede rechazar un resultado, así que el ajuste nunca se aplicaría.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.ExhaustedResult;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(onExhaustedResult = ExhaustedResult.THROW)
    String price(String sku);
}

Corrección: añade un clasificador que rechace los resultados a reintentar, o quita onExhaustedResult.

package com.acme;

import dev.resilience4cata.Classifier;
import dev.resilience4cata.annotation.ExhaustedResult;
import dev.resilience4cata.annotation.Retry;

public interface PriceClient {
    @Retry(classifier = NotEmpty.class, onExhaustedResult = ExhaustedResult.THROW)
    String price(String sku);

    final class NotEmpty implements Classifier<String> {
        @Override
        public Outcome classify(String result, Throwable failure) {
            return failure != null || result == null || result.isEmpty() ? Outcome.FAILURE : Outcome.SUCCESS;
        }
    }
}

Procesador de anotaciones: fallbacks (R4C-FB)

Un @Fallback nombra una clase handler, @Fallback(MyHandler.class), o métodos hermanos, @Fallback(method = "name"). Una clase handler debe cumplir las mismas reglas que un clasificador: concreta, no genérica, accesible, con constructor sin argumentos, e implementar FallbackHandler<T> para el tipo de resultado del método.

R4C-FB-001

Causa: @Fallback no define ni una clase handler ni un nombre de método, o define ambos.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback
    String find(String id);
}

Corrección: define exactamente uno de los dos.

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(method = "findFallback")
    String find(String id);

    default String findFallback(String id, Exception e) {
        return "unknown";
    }
}

R4C-FB-002

Causa: @Fallback(method = "...") nombra un método que no existe en la interfaz (o clase). El mensaje sugiere el nombre existente más parecido.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(method = "findFallbak")
    String find(String id);

    default String findFallback(String id, Exception e) {
        return "unknown";
    }
}

Corrección: corrige el nombre o añade el método. Recibe los mismos parámetros que el método anotado, opcionalmente seguidos de la excepción, y devuelve el mismo tipo.

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(method = "findFallback")
    String find(String id);

    default String findFallback(String id, Exception e) {
        return "unknown";
    }
}

R4C-FB-003

Causa: existen métodos con el nombre del fallback, pero ninguno sirve. El mensaje lista cada candidato y por qué se descartó: privado (la clase generada no puede llamarlo), tipo de retorno incorrecto, una excepción checked que el método anotado no declara, parámetros de tipo distintos, o un parámetro de excepción que no es una clase. No se listan los candidatos con otros parámetros.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(method = "findFallback")
    String find(String id);

    private String findFallback(String id, Exception e) {
        return "unknown";
    }
}

Corrección: en una interfaz, usa un método default o estático; en una clase, uno no privado. Ajusta la firma a la que muestra el mensaje.

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(method = "findFallback")
    String find(String id);

    default String findFallback(String id, Exception e) {
        return "unknown";
    }
}

R4C-FB-004

Causa: @Fallback nombra una clase handler, pero dev.resilience4cata.FallbackHandler no está en el classpath de compilación.

Incorrecto: compilar esto sin resilience4cata-core en el classpath:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

Corrección: añade la dependencia de core:

dependencies {
    implementation 'dev.resilience4cata:resilience4cata-core:<version>'
}

R4C-FB-005

Causa: el handler que nombra @Fallback es una interfaz, una anotación o un enum en lugar de una clase.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(FallbackHandler.class)
    String find(String id);
}

Corrección: nombra una clase que implemente FallbackHandler<T>.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-006

Causa: la clase handler es abstracta.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    abstract class Unknown implements FallbackHandler<String> {
    }
}

Corrección: usa una clase concreta.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-007

Causa: la clase handler declara parámetros de tipo.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown<T> implements FallbackHandler<T> {
        @Override
        public T apply(FallbackContext context) {
            return null;
        }
    }
}

Corrección: implementa FallbackHandler<T> con un T concreto. Para un método genérico, usa un método hermano (ver R4C-FB-014).

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-008

Causa: la clase handler (o una clase que la contiene) es privada, o package-private en otro paquete, así que la clase generada no puede crearla.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public final class Clients {
    interface UserClient {
        @Fallback(Unknown.class)
        String find(String id);
    }

    private static final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

Corrección: hazla pública, o package-private en el paquete de la interfaz.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public final class Clients {
    interface UserClient {
        @Fallback(Unknown.class)
        String find(String id);
    }

    static final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-009

Causa: el handler es una clase interna (anidada no estática).

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public final class Clients {
    interface UserClient {
        @Fallback(Unknown.class)
        String find(String id);
    }

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

Corrección: hazla static.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public final class Clients {
    interface UserClient {
        @Fallback(Unknown.class)
        String find(String id);
    }

    static final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-010

Causa: el handler no tiene un constructor sin argumentos que la clase generada pueda llamar.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        private Unknown() {
        }

        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

Corrección: añade un constructor sin argumentos público (o package-private, en el paquete de la interfaz), o quita el privado.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-011

Causa: la clase handler no implementa dev.resilience4cata.FallbackHandler.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(String.class)
    String find(String id);
}

Corrección: nombra una clase que implemente FallbackHandler<T>.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-012

Causa: el handler implementa el tipo raw FallbackHandler.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler {
        @Override
        public Object apply(FallbackContext context) {
            return "unknown";
        }
    }
}

Corrección: implementa FallbackHandler<T> con el tipo de resultado del método, como sugiere el mensaje.

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-013

Causa: el handler implementa FallbackHandler<X>, pero X no es asignable al tipo de retorno del método. En un método asíncrono que devuelve un stage de T, X puede ser T o CompletionStage<T>.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<Integer> {
        @Override
        public Integer apply(FallbackContext context) {
            return 0;
        }
    }
}

Corrección: implementa FallbackHandler para el tipo de retorno del método (con boxing, si es primitivo).

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface UserClient {
    @Fallback(Unknown.class)
    String find(String id);

    final class Unknown implements FallbackHandler<String> {
        @Override
        public String apply(FallbackContext context) {
            return "unknown";
        }
    }
}

R4C-FB-014

Causa: una clase handler en un método genérico cuyo tipo de retorno depende de los parámetros de tipo del método. Un handler implementa FallbackHandler<X> para un único X, que no encaja con todas las instanciaciones sin un cast no comprobado.

Incorrecto:

package com.acme;

import dev.resilience4cata.FallbackContext;
import dev.resilience4cata.FallbackHandler;
import dev.resilience4cata.annotation.Fallback;

public interface Repository {
    @Fallback(Nothing.class)
    <T> T load(Class<T> type, String id);

    final class Nothing implements FallbackHandler<Object> {
        @Override
        public Object apply(FallbackContext context) {
            return null;
        }
    }
}

Corrección: usa un método de fallback hermano que declare los mismos parámetros de tipo.

package com.acme;

import dev.resilience4cata.annotation.Fallback;

public interface Repository {
    @Fallback(method = "loadFallback")
    <T> T load(Class<T> type, String id);

    default <T> T loadFallback(Class<T> type, String id, Exception e) {
        return null;
    }
}

Procesador de anotaciones: clases anotadas (R4C-SUB)

Una clase anotada recibe una subclase generada, <Class>$Resilient, que sobrescribe los métodos anotados y se crea con Resilient.create(...). Por eso la clase debe poder extenderse y los métodos anotados deben poder sobrescribirse.

R4C-SUB-001

Causa: la clase anotada es final.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public final class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

Corrección: quita final, o mueve las anotaciones a una interfaz y envuelve la implementación con Resilient.of(...).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

R4C-SUB-002

Causa: la clase anotada es sealed, así que la subclase generada no es una subclase permitida.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public sealed class PriceService permits CachedPriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

final class CachedPriceService extends PriceService {
}

Corrección: hazla non-sealed (o una clase normal), o usa una interfaz y Resilient.of(...).

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

final class CachedPriceService extends PriceService {
}

R4C-SUB-003

Causa: la clase anotada es abstracta. La subclase generada debe poder instanciarse y llama a super en cada método.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public abstract class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

Corrección: anota una subclase concreta, o una interfaz.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

R4C-SUB-004

Causa: la clase anotada, o una clase que la contiene, es privada.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public final class Services {
    private static class PriceService {
        @Retry
        public String price(String sku) {
            return "1";
        }
    }
}

Corrección: hazla package-private, protected o pública.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public final class Services {
    static class PriceService {
        @Retry
        public String price(String sku) {
            return "1";
        }
    }
}

R4C-SUB-005

Causa: la clase anotada es una clase interna (anidada no estática): su subclase generada necesitaría una instancia de la clase contenedora.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class Services {
    class PriceService {
        @Retry
        public String price(String sku) {
            return "1";
        }
    }
}

Corrección: hazla una clase anidada estática.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class Services {
    static class PriceService {
        @Retry
        public String price(String sku) {
            return "1";
        }
    }
}

R4C-SUB-006

Causa: todos los constructores de la clase anotada son privados, así que la subclase generada no puede llamar a ninguno.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    private PriceService() {
    }

    @Retry
    public String price(String sku) {
        return "1";
    }
}

Corrección: haz al menos un constructor package-private, protected o público.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    protected PriceService() {
    }

    @Retry
    public String price(String sku) {
        return "1";
    }
}

R4C-SUB-007

Causa: un método anotado (en la clase, una superclase o una interfaz que implementa) es final, así que la subclase no puede sobrescribirlo y sus anotaciones se ignorarían.

Incorrecto:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    @Retry
    public final String price(String sku) {
        return "1";
    }
}

Corrección: quita final, o quita las anotaciones.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    @Retry
    public String price(String sku) {
        return "1";
    }
}

R4C-SUB-008

Causa: un método anotado es package-private en una superclase de otro paquete. La subclase generada, en el paquete de la clase anotada, no puede sobrescribirlo, así que sus anotaciones se ignorarían.

Incorrecto:

// com/acme/base/BaseService.java
package com.acme.base;

import dev.resilience4cata.annotation.Retry;

public class BaseService {
    @Retry
    String price(String sku) {
        return "1";
    }
}

// com/acme/PriceService.java
package com.acme;

import com.acme.base.BaseService;
import dev.resilience4cata.annotation.Retry;

public class PriceService extends BaseService {
    @Retry
    public String name() {
        return "prices";
    }
}

Corrección: haz el método protected o público.

// com/acme/base/BaseService.java
package com.acme.base;

import dev.resilience4cata.annotation.Retry;

public class BaseService {
    @Retry
    protected String price(String sku) {
        return "1";
    }
}

// com/acme/PriceService.java
package com.acme;

import com.acme.base.BaseService;
import dev.resilience4cata.annotation.Retry;

public class PriceService extends BaseService {
    @Retry
    public String name() {
        return "prices";
    }
}

Ejecución: Resilient y Pipeline (R4C-RT)

Los lanza resilience4cata-core al envolver un servicio o construir un pipeline. Los tipos de excepción están documentados en cada método; el código va al principio del mensaje.

R4C-RT-001

Causa: Resilient.of(type, ...) con un type que es una clase, no una interfaz (IllegalArgumentException).

Incorrecto: Resilient.of(PriceService.class, new PriceService(), registry) donde PriceService es una clase anotada.

Corrección: para una clase anotada, usa Resilient.create(PriceService.class, registry); Resilient.of recibe la interfaz anotada.

R4C-RT-002

Causa: el objeto pasado a Resilient.of no implementa la interfaz (IllegalArgumentException). Solo puede pasar mediante tipos raw o casts no comprobados.

Corrección: pasa una implementación de la interfaz.

R4C-RT-003

Causa: no se encontró la clase generada <type>$Resilient (IllegalStateException): el tipo no tiene anotaciones de resiliencia, o el procesador de anotaciones no se ejecutó al compilarlo.

Incorrecto: un build que solo añade las anotaciones:

dependencies {
    compileOnly 'dev.resilience4cata:resilience4cata-annotations:<version>'
    implementation 'dev.resilience4cata:resilience4cata-core:<version>'
}

Corrección: añade el procesador, que es obligatorio (no hay alternativa por reflexión):

dependencies {
    compileOnly 'dev.resilience4cata:resilience4cata-annotations:<version>'
    implementation 'dev.resilience4cata:resilience4cata-core:<version>'
    annotationProcessor 'dev.resilience4cata:resilience4cata-processor:<version>'
}

En una imagen nativa de GraalVM, el mismo error significa que el procesador se ejecutó pero la imagen no alcanza la clase por reflexión: los metadatos de alcanzabilidad que escribe junto a los archivos de clase, META-INF/native-image/dev.resilience4cata.generated/<type>$Resilient/reachability-metadata.json, no estaban en el class path del build de la imagen, o un paso del build (shading, un filtro de recursos) los eliminó. El mensaje termina entonces con una frase que nombra ese archivo. Conserva la salida del procesador, metadatos incluidos, en el jar o directorio que lee native-image; ver Imagen nativa.

R4C-RT-004

Causa: la clase generada no tiene el constructor que espera esta versión de resilience4cata-core, o una subclase generada no extiende la clase anotada (IllegalStateException). La generó una versión incompatible del procesador.

Corrección: usa la misma versión para resilience4cata-core y resilience4cata-processor (el BOM las mantiene alineadas) y recompila desde cero.

R4C-RT-005

Causa: resilience4cata-core no puede acceder a la clase generada (IllegalStateException), normalmente porque la aplicación es un módulo con nombre que no exporta el paquete a core.

Corrección: añade al module-info.java de la aplicación:

exports com.acme to dev.resilience4cata.core;

R4C-RT-006

Causa: el constructor de la clase generada falló con una excepción checked, o no se pudo instanciar (IllegalStateException, con el fallo como causa). Las excepciones unchecked del constructor, como una configuración inválida, se relanzan tal cual.

Corrección: mira la causa; suele venir del constructor de un clasificador o de un handler de fallback.

R4C-RT-007

Causa: Resilient.create(type, ...) con una interfaz (IllegalArgumentException).

Corrección: envuelve una implementación: Resilient.of(PriceClient.class, implementation, registry).

R4C-RT-008

Causa: Resilient.create(type, ...) con un tipo que no puede tener una subclase generada: final, abstracto, local, anónimo, un enum, un record, un array o un primitivo (IllegalArgumentException). El procesador reporta casi todos estos casos al compilar (R4C-SUB-001 y siguientes).

Corrección: anota una clase de primer nivel o anidada estática que no sea final ni abstracta, o una interfaz y usa Resilient.of(...).

R4C-RT-009

Causa: ningún constructor de la clase anotada acepta los argumentos pasados a Resilient.create (IllegalArgumentException). El mensaje lista los constructores.

Corrección: pasa argumentos que encajen con un constructor, o llama directamente al constructor generado, que comprueba el compilador: new PriceService$Resilient(registry, ...).

R4C-RT-010

Causa: más de un constructor acepta los argumentos pasados a Resilient.create, por ejemplo un único null (IllegalArgumentException).

Corrección: llama directamente al constructor generado, con un cast para elegir la sobrecarga: new PriceService$Resilient(registry, (String) null).

R4C-RT-011

Causa: Pipeline.builder(registry, id) con un id en blanco (IllegalArgumentException).

Corrección: pasa el id que nombra las instancias de política del pipeline, por ejemplo "payments".

R4C-RT-012

Causa: una parte de Pipeline.Builder se define dos veces: una política, un clasificador, onExhaustedResult o un fallback (IllegalStateException).

Incorrecto: Pipeline.builder(registry, "payments").retry().retry("five")

Corrección: define cada parte una vez por builder; pasa los ajustes en la única llamada: retry("five", b -> b.maxAttempts(3)).

R4C-RT-013

Causa: se definió fallbackApplyOn o fallbackSkipOn, pero el pipeline no tiene fallback (IllegalStateException desde build()).

Corrección: añade un fallback (fallback, fallbackHandler, fallbackAsync) o quita la selección.

R4C-RT-014

Causa: se definió classifier(...), pero el pipeline no tiene ni retry ni circuit breaker (IllegalStateException desde build()).

Corrección: añade la política a la que va destinado el clasificador, o quítalo.

R4C-RT-015

Causa: se definió retryClassifier(...), pero el pipeline no tiene retry (IllegalStateException desde build()).

Corrección: añade un retry, o quita el clasificador del retry.

R4C-RT-016

Causa: se definió onExhaustedResult(...), pero el pipeline no tiene un retry con clasificador (IllegalStateException desde build()). Solo un clasificador puede rechazar un resultado. El equivalente en anotaciones es R4C-CLS-012.

Corrección: añade un retry y un clasificador (classifier o retryClassifier), o quita onExhaustedResult.

R4C-RT-017

Causa: se definió circuitBreakerClassifier(...), pero el pipeline no tiene circuit breaker (IllegalStateException desde build()).

Corrección: añade un circuit breaker, o quita el clasificador del circuit breaker.

R4C-RT-018

Causa: se llama a execute(...) o run(...) sobre un pipeline que tiene un hedge (IllegalStateException). Un hedge vuelve a lanzar la llamada mientras el primer intento sigue en curso, y eso necesita una llamada que devuelva un CompletionStage.

Corrección: llama a executeAsync(() -> client.callAsync(...)), o quita .hedge(...) del pipeline.

R4C-RT-019

Causa: el pipeline tiene a la vez .retry(...) y .hedge(...) (IllegalStateException desde build()).

Corrección: quédate con uno: un hedge ya lanza un intento nuevo cuando uno falla (hedgeOn), y un retry alrededor multiplicaría los intentos. Es el equivalente en ejecución de R4C-HDG-002.

Ejecución: registro y configuración de políticas (R4C-REG)

Los lanza resilience4cata-core cuando se construye la configuración de una política (RetryConfig.builder() y los demás builders *Config), cuando se construye un ResilienceRegistry, y cuando el registro crea o busca una instancia de política: al envolver un servicio (Resilient.of, Resilient.create) o al construir un pipeline (Pipeline.Builder.build(), que los deja pasar sin cambios). Los tipos de excepción están documentados en cada método; el código va al inicio del mensaje. La configuración externa (ResilienceConfig) valida sus valores antes de que lleguen al registro y los reporta como R4C-CFG-006, con las claves que los definieron.

R4C-REG-001

Causa: un builder *Config rechazó un valor (IllegalArgumentException desde build()), por ejemplo un maxAttempts menor que 1, una duración negativa, un maxDelay más corto que el delay, o un umbral de tasa fuera de (0, 100]. El mensaje nombra el atributo y el valor.

Incorrecto: RetryConfig.builder().maxAttempts(0).build()

Corrección: usa un valor dentro del rango que indica el mensaje: RetryConfig.builder().maxAttempts(1).build().

R4C-REG-002

Causa: ResilienceRegistry.Builder.build() encontró una configuración por defecto o con nombre inválida (IllegalArgumentException, con el error R4C-REG-001 como causa). El mensaje nombra la configuración (o los valores por defecto) y dónde se definió.

Incorrecto:

ResilienceRegistry.builder()
        .retry("slow", r -> r.delay(Duration.ofSeconds(2)).maxDelay(Duration.ofSeconds(1)))
        .build();

Corrección: haz que los valores de esa configuración sean válidos en conjunto. Una configuración con nombre parte de los valores por defecto (retryDefaults(...)), luego aplica retry("slow", ...) y después retryOverlay("slow", ...) (configs.slow.retry.* en la configuración externa):

ResilienceRegistry.builder()
        .retry("slow", r -> r.delay(Duration.ofSeconds(1)).maxDelay(Duration.ofSeconds(2)))
        .build();

R4C-REG-003

Causa: la configuración de una instancia de política es inválida una vez que se combina todo lo que se le aplica (IllegalArgumentException, con el error R4C-REG-001 como causa): su configuración con nombre (o los valores por defecto), luego los valores de su anotación o paso de pipeline, y después sus ajustes por instancia (retryInstance(id, ...), instances.<id>.* en la configuración externa). Se lanza cuando se crea la instancia: desde Resilient.of/Resilient.create o desde Pipeline.Builder.build().

Incorrecto: un ajuste por instancia que no encaja con los valores propios de la instancia:

ResilienceRegistry registry = ResilienceRegistry.builder()
        .retryInstance("payments", r -> r.delay(Duration.ofSeconds(30)))
        .build();
Pipeline.builder(registry, "payments").retry(r -> r.maxDelay(Duration.ofSeconds(20))).build();

Corrección: cambia el ajuste, o el valor con el que choca, para que la combinación sea válida, por ejemplo retryInstance("payments", r -> r.delay(Duration.ofSeconds(10))).

R4C-REG-004

Causa: config = "name" (en una anotación o en un paso de pipeline), o retryConfig("name") y las demás búsquedas *Config(String), nombra una configuración que el registro no tiene (IllegalArgumentException). El mensaje lista las configuraciones conocidas y sugiere la más parecida ("Did you mean …?").

Incorrecto: @Retry(config = "fiev") con un registro que solo registra retry("five", ...).

Corrección: corrige el nombre, o registra la configuración: ResilienceRegistry.builder().retry("fiev", r -> ...), o configs.fiev.retry.* en la configuración externa.

R4C-REG-005

Causa: la misma configuración con nombre se registra dos veces en un builder, por ejemplo con dos llamadas a retry("five", ...) (IllegalArgumentException).

Corrección: regístrala una sola vez. Para cambiar algunos de sus atributos en otro lugar, usa el overlay, que se aplica encima: retryOverlay("five", r -> r.maxAttempts(3)).

R4C-REG-006

Causa: una configuración con nombre u overlay se registra con el nombre "" o "default", por ejemplo retry("default", ...) (IllegalArgumentException). Ambos nombres están reservados: significan los valores por defecto.

Corrección: cambia los valores por defecto con retryDefaults(...) (defaults.retry.* en la configuración externa), o dale otro nombre a la configuración.

R4C-REG-007

Causa: retryInstance("", ...) u otra llamada *Instance(...) con un id de instancia vacío (IllegalArgumentException).

Corrección: pasa el id de la instancia: el name de su anotación, el id del pipeline, o el id derivado <interface>#<method>(<parameter types>), por ejemplo com.acme.PriceClient#price(java.lang.String).

R4C-REG-008

Causa: dos puntos de llamada comparten una instancia de retry, circuit breaker, rate limiter o bulkhead, por el mismo name en sus anotaciones o el mismo id de pipeline, pero resuelven configuraciones distintas (IllegalStateException). El mensaje muestra ambas configuraciones. Las instancias con el mismo nombre se comparten, así que deben coincidir.

Incorrecto:

interface Inventory {
    @Retry(name = "backend", maxAttempts = 3)
    String stock(String sku);

    @Retry(name = "backend", maxAttempts = 5)
    String reserve(String sku);
}

Corrección: da nombres distintos a los puntos de llamada si deben ser independientes, o haz idéntica su configuración (por ejemplo, el mismo config = "..." y ningún atributo distinto) si deben compartir una instancia.

R4C-REG-009

Causa: registry.circuitBreaker(id), registry.rateLimiter(id) o registry.bulkhead(id) con un id que ninguna instancia de este registro tiene (IllegalArgumentException). El mensaje lista los ids conocidos y sugiere el más parecido. Las instancias se crean cuando se envuelve su interfaz (Resilient.of), se crea su clase (Resilient.create) o se construye su pipeline, así que buscarlas antes también falla.

Corrección: usa el name de la anotación (o el id del pipeline), o el id derivado <interface>#<method>(<parameter types>), y búscala después de crear el servicio o el pipeline.

Ejecución: configuración externa (R4C-CFG)

ResilienceConfig.build() reporta todos los problemas a la vez en una única ConfigException; cada línea empieza con su código y nombra la clave y su origen (archivo y línea, variable de entorno o propiedad del sistema).

R4C-CFG-001

Causa: una clave desconocida, mal formada o incompleta: política o atributo desconocido, segmento vacío, una clave que se corta antes de tiempo. El mensaje sugiere la clave válida más parecida.

Incorrecto:

configs.external.circuit-breaker.wait-in-opne=10s

Corrección: usa una de las formas de clave que lista el mensaje: defaults.<policy>.<attribute>, configs.<name>.<policy>.<attribute>, configs.<name>.extends, instances.<id>.<policy>.<attribute>, profiles.<profile>.… o profiles.active.

configs.external.circuit-breaker.wait-in-open=10s

R4C-CFG-002

Causa: un valor que no se puede interpretar para su atributo: un número, booleano, constante de enum o duración.

Incorrecto:

defaults.retry.delay=fast

Corrección: usa un valor del tipo del atributo; las duraciones usan la misma gramática que las anotaciones ("250ms", "2s", "PT2S", "0").

defaults.retry.delay=250ms

R4C-CFG-003

Causa: una lista de excepciones (retry-on, abort-on, record-on, ignore-on) nombra una clase que el class loader no encuentra, o una clase que no es Throwable.

Incorrecto:

defaults.retry.retry-on=java.io.IOExeption

Corrección: usa nombres completos de clases de excepción visibles para el class loader de la aplicación.

defaults.retry.retry-on=java.io.IOException

R4C-CFG-004

Causa: un nombre de config o de perfil inválido, o reservado (default para un config, active para un perfil). Los nombres usan minúsculas y dígitos separados por guiones simples, para que correspondan a variables de entorno.

Incorrecto:

profiles.active=Test

Corrección: cámbialo, por ejemplo a test.

profiles.active=test

R4C-CFG-005

Causa: la misma clave se define dos veces en un mismo origen (por ejemplo dos veces en un archivo, o con dos grafías como max-attempts y maxAttempts).

Corrección: deja una de las dos. Orígenes distintos sí pueden definir la misma clave; decide el orden de precedencia.

R4C-CFG-006

Causa: los valores son válidos uno a uno pero no juntos, por ejemplo un max-delay menor que el delay, o max-attempts=0 (el builder del config los rechazó). El mensaje nombra las claves implicadas.

Incorrecto:

configs.slow.retry.delay=2s
configs.slow.retry.max-delay=1s

Corrección: haz que los valores sean coherentes, como describe el mensaje.

configs.slow.retry.delay=1s
configs.slow.retry.max-delay=2s

R4C-CFG-007

Causa: configs.<name>.extends nombra un config que no está definido en la configuración externa, o varios configs se extienden entre sí en un ciclo.

Incorrecto:

configs.batch.extends=slwo

Corrección: extiende un config existente (el mensaje sugiere el nombre más parecido y lista los conocidos) y rompe cualquier ciclo.

R4C-CFG-008

Causa: reservado; la versión actual no lo reporta.

R4C-CFG-009

Causa: no se puede leer un origen de configuración: un recurso del classpath o un archivo que no existe, o un error de E/S.

Corrección: revisa la ruta, o no añadas el origen si es opcional.

R4C-CFG-010

Causa: una variable de entorno corresponde a más de una clave, porque _ representa tanto . como -.

Corrección: define esa clave en un archivo de propiedades o como propiedad del sistema.

R4C-CFG-011

Causa: retirado; ya no se reporta. Un config con nombre definido a la vez en código y externamente ahora se fusiona, y ganan los valores externos.

R4C-CFG-012

Causa: un id de instancia mal formado en una clave instances.: un id que contiene . y no va entre comillas, un id vacío, una " sin cerrar o mal colocada, comillas alrededor de algo que no es el id, o un id que empieza o termina con espacios.

Incorrecto:

instances.com.acme.Api#get(java.lang.String).retry.max-attempts=5

Corrección: pon el id de instancia entre comillas.

instances."com.acme.Api#get(java.lang.String)".retry.max-attempts=5

R4C-CFG-013

Causa: config.verifyInstances(registry) encontró un ajuste instances. que no corresponde a ninguna instancia de esa política en el registry (lo reporta verifyInstances, no build(), porque las instancias solo existen una vez envueltos los servicios). El mensaje sugiere el id más parecido.

Corrección: usa un id de instancia existente: el name de la política, o <interface>#<method>(<parameter types>) si no tiene.

R4C-CFG-014

Causa: config.verifyInstances(registry) encontró un ajuste instances. de un atributo que la anotación de la instancia fija con locked = true (o que un pipeline construido con .locked() fija en su paso). El registry conservó el valor de la anotación e ignoró el ajuste; el mensaje muestra la configuración que usa la instancia.

Corrección: quita la clave, o quita locked = true de la anotación si los operadores deben poder cambiar ese valor. Ver Bloquear una instancia.

Ejecución: integración con Spring Boot (R4C-SPR)

resilience4cata-spring revisa los beans al arrancar y detiene el arranque con estos códigos cuando un bean perdería su protección sin aviso. Spring Boot muestra el mensaje como Description y la corrección como Action de su informe "APPLICATION FAILED TO START". Consulta la guía de Spring Boot.

R4C-SPR-001

Causa: un bean es una instancia de una clase anotada C pero no de la C$Resilient generada, así que sus políticas nunca se ejecutarían: un método @Bean devolvió new C(...), el bean se registró con un supplier, o es una subclase de C sin anotaciones propias (BeanCreationException).

[R4C-SPR-001] Bean 'pricing' is an instance of com.acme.PricingService, which has resilience4cata annotations, but not of the generated com.acme.PricingService$Resilient, so its policies would not run. ...

Corrección: registra la clase misma (escaneo de componentes, @Import), para que Spring instancie C$Resilient; o devuelve new C$Resilient(registry, ...) desde el método @Bean, con el ResilienceRegistry inyectado como parámetro; o anota la subclase. Para dejar el bean sin protección a propósito, pon su nombre en resilience4cata.spring.exclude.

R4C-SPR-002

Causa: una clase @Configuration con métodos @Bean tiene además anotaciones de resilience4cata. Spring instancia su propia subclase CGLIB de una clase así, de modo que la subclase generada no puede usarse.

Corrección: mueve los métodos anotados a una clase @Component o @Service, o declara la configuración con @Configuration(proxyBeanMethods = false), que Spring no extiende.

R4C-SPR-003

Causa: un bean implementa dos o más interfaces anotadas y ninguna extiende a todas las demás (BeanCreationException). El envoltorio generado de una interfaz implementa solo esa interfaz, así que el bean ya no podría inyectarse por las otras.

Corrección: anota la clase en su lugar (su subclase generada conserva todas las interfaces y sigue siendo el tipo del bean), o declara una interfaz que extienda a las demás e implementa esa. Para envolver el bean tú mismo, ponlo en resilience4cata.spring.exclude y define un @Bean que devuelva Resilient.of(...).

R4C-SPR-004

Causa: una clase anotada tiene un método con @Retry o @Hedge en vigor (en el método, la clase, una superclase, una interfaz o un bundle) que además es transaccional: @Transactional de Spring o de Jakarta con una propagación que se une a una transacción o la inicia (REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NESTED) (BeanCreationException). El proxy de transacciones envuelve la subclase generada, así que todos los intentos se ejecutarían en la misma transacción, y un intento que falla y la marca rollback-only hace que el commit de un intento posterior exitoso falle con UnexpectedRollbackException.

[R4C-SPR-004] Bean 'ledger' (com.acme.Ledger) has transactional methods that are retried or hedged: @Retry on post (REQUIRED). ...

Corrección: declara la política en una interfaz que implemente el bean (su envoltorio queda fuera del proxy de transacciones: una transacción por intento); o mueve @Transactional a otro bean al que llame el método reintentado; o, si se quiere a propósito una sola transacción compartida por todos los intentos, pon resilience4cata.spring.allow-retry-inside-transaction=true. NOT_SUPPORTED y NEVER no se reportan.

Ejecución: integración con Micronaut (R4C-MN)

resilience4cata-micronaut revisa los beans y falla con estos códigos cuando un bean perdería su protección sin aviso. Los errores de @ResilientBean (001 a 003) detienen el arranque: cada definición de bean anotada con @ResilientBean se revisa al arrancar el contexto, antes de crear ninguna. Los demás se reportan al crear el bean, que es al arrancar para los beans @Context y en el primer uso para los demás (Micronaut crea los beans de forma perezosa), como una ResilienceBeanException dentro de la BeanInstantiationException de Micronaut. Consulta la guía de Micronaut.

R4C-MN-001

Causa: un bean anotado con @ResilientBean(I.class) se expone con otros tipos además de I, por ejemplo porque también declara @Bean(typed = {I.class, Impl.class}). @ResilientBean limita los tipos del bean a I, porque el envoltorio generado que reemplaza al bean solo implementa I: inyectarlo con otro tipo fallaría o se saltaría las políticas.

[R4C-MN-001] Bean com.acme.HttpPayments is annotated @ResilientBean(Payments.class) but is exposed as [com.acme.HttpPayments, com.acme.Payments]. ...

Corrección: quita @Bean(typed = ...) (o @Type) del bean e inyecta la interfaz.

R4C-MN-002

Causa: @ResilientBean(X.class) nombra un tipo que no es una interfaz, o una interfaz que el bean no implementa, así que ningún envoltorio generado puede protegerlo.

Corrección: nombra la interfaz anotada que implementa el bean. Para una clase anotada, quita @ResilientBean: compila con -Ar4c.inject=micronaut para que Micronaut cree su subclase generada, o devuelve new C$Resilient(registry, ...) desde un método de un @Factory (clases anotadas).

R4C-MN-003

Causa: un bean anotado con @ResilientBean tiene un ámbito que Micronaut envuelve en un proxy (@Refreshable, @RequestScope, @ThreadLocal, cualquier @ScopedProxy). Ese proxy busca su destino por la clase del bean, y el envoltorio generado no lo es.

Corrección: haz el bean @Singleton, @Prototype o @Context, o deja el bean con ámbito sin protección e inyéctalo en un singleton que implemente la interfaz anotada.

R4C-MN-004

Causa: un bean es una instancia de una clase anotada C pero no de la C$Resilient generada, así que sus políticas nunca se ejecutarían: Micronaut creó C por su cuenta (tiene una anotación de ámbito o un constructor @Inject, y el código se compiló sin -Ar4c.inject=micronaut), o un método de un @Factory devolvió new C(...). También se reporta cuando Micronaut crea C$Resilient desde su propia definición aunque se compiló sin la opción: su constructor repite el @Inject de C, así que es un bean, pero sin el ámbito ni los calificadores de C (un @Singleton pasaría a ser una instancia nueva en cada inyección).

[R4C-MN-004] Bean com.acme.InventoryService is an instance of com.acme.InventoryService, which has resilience4cata annotations, but not of the generated InventoryService$Resilient, so its policies would not run. ...

Corrección: compila con -Ar4c.inject=micronaut, para que Micronaut cree C$Resilient en lugar de C; o quita la anotación de ámbito (y @Inject) de C y devuelve new C$Resilient(registry, ...) desde un método de un @Factory con tipo C. Para dejar el bean sin protección a propósito, pon el nombre de la clase en resilience4cata.micronaut.exclude.

R4C-MN-005

Causa: una clase anotada compilada con -Ar4c.inject=micronaut tiene un método con @Retry o @Hedge en vigor que además es @Transactional (el de Micronaut, o el de Jakarta traducido a él) con una propagación que se une a una transacción o la inicia (REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NESTED). La subclase generada repite @Transactional, así que el proxy de transacciones de Micronaut la envuelve: todos los intentos se ejecutarían en la misma transacción, y un intento que falla y la marca rollback-only hace que el commit de un intento posterior exitoso falle.

[R4C-MN-005] Bean com.acme.Ledger has transactional methods that are retried or hedged: @Retry on post (REQUIRED). ...

Corrección: declara la política en una interfaz que implemente el bean y anota el bean con @ResilientBean (el envoltorio queda fuera del proxy de transacciones: una transacción por intento); o mueve @Transactional a otro bean al que llame el método reintentado; o, si se quiere a propósito una sola transacción compartida por todos los intentos, pon resilience4cata.micronaut.allow-retry-inside-transaction=true. NOT_SUPPORTED y NEVER no se reportan. La revisión lee el archivo de clase generado, así que una imagen nativa la omite.

R4C-MN-006

Causa: una opción de la integración con Micronaut tiene un valor inválido, por ejemplo resilience4cata.micronaut.verify-instances: strict. El contexto no arranca.

[R4C-MN-006] Invalid value 'strict' for resilience4cata.micronaut.verify-instances. Use fail, warn or off.

Corrección: usa uno de los valores que indica el mensaje.

Compilación: extensión de Quarkus (R4C-QK)

resilience4cata-quarkus revisa la aplicación mientras Quarkus la construye (quarkusBuild, el modo dev, cada @QuarkusTest) y detiene la construcción con estos códigos cuando un bean no quedaría protegido como se declaró, o quedaría protegido dos veces. Quarkus los muestra entre sus "deployment problems". Consulta la guía de Quarkus.

R4C-QK-001

Causa: un método protegido por resilience4cata lleva además anotaciones de MicroProfile o SmallRye Fault Tolerance (org.eclipse.microprofile.faulttolerance.*, io.smallrye.faulttolerance.api.*), en el método o en su clase; para una interfaz anotada, también en el método de la clase que la implementa. Los nombres simples son los mismos (@Retry, @CircuitBreaker, @Timeout, @Bulkhead, @Fallback), así que suele ser un import equivocado. Con quarkus-smallrye-fault-tolerance instalado se aplicarían las dos bibliotecas, así que la construcción falla; sin él, las anotaciones de Fault Tolerance no hacen nada y el mismo mensaje se registra como advertencia.

[R4C-QK-001] Methods protected by resilience4cata also carry MicroProfile Fault Tolerance annotations: com.acme.CatalogClient.fetch(). SmallRye Fault Tolerance would apply its interceptor as well, so both libraries would retry, time out and count the same calls. ...

Corrección: usa una sola biblioteca por método: quita la otra anotación o corrige el import. Consulta SmallRye Fault Tolerance frente a resilience4cata.

R4C-QK-002

Causa: una clase anotada tiene un método con @Retry o @Hedge en vigor que además es @Transactional (Jakarta Transactions, con Narayana) con un tipo que se une a una transacción o la inicia (REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS). El interceptor de transacciones envuelve la subclase generada, así que todos los intentos se ejecutarían en la misma transacción, y un intento que falla y la marca rollback-only hace que el commit de un intento posterior exitoso falle.

[R4C-QK-002] Bean com.acme.Ledger has transactional methods that are retried or hedged: @Retry on post (REQUIRED). ...

Corrección: declara la política en una interfaz que implemente el bean (su envoltorio queda fuera del interceptor de transacciones: una transacción por intento); o mueve @Transactional a otro bean al que llame el método reintentado; o, si se quiere a propósito una sola transacción compartida por todos los intentos, pon quarkus.resilience4cata.allow-retry-inside-transaction=true. NOT_SUPPORTED y NEVER no se reportan.

R4C-QK-003

Causa: un bean implementa dos o más interfaces anotadas y ninguna extiende a todas las demás. El envoltorio generado de una interfaz implementa solo esa interfaz, así que el bean solo puede inyectarse a través de un envoltorio.

Corrección: declara una interfaz que extienda a las demás e implementa esa, o anota la clase en su lugar. Para envolver el bean tú mismo, pon su clase en quarkus.resilience4cata.beans.exclude y devuelve Resilient.of(...) desde un método productor.

R4C-QK-004

Causa: una clase anotada declara métodos o campos productores (@Produces). La subclase generada sustituye a la clase como bean, y los productores de CDI no se heredan, así que desaparecerían.

Corrección: mueve los productores a otro bean, o pon la clase en quarkus.resilience4cata.beans.exclude.

R4C-QK-005

Causa: una clase anotada es un bean de ámbito normal (@ApplicationScoped, @RequestScoped, @SessionScoped) y no tiene un constructor no privado sin parámetros. El proxy de cliente que Quarkus pone delante de la subclase generada necesita uno, y Quarkus solo puede añadirlo a la subclase si la clase lo tiene.

[R4C-QK-005] com.acme.OrderService is a normal-scoped bean (@ApplicationScoped) with resilience4cata annotations, but has no non-private constructor without parameters. ...

Corrección: añade un constructor sin parámetros (basta con que sea package-private: solo lo usa el proxy), o usa inyección por campo en lugar de por constructor, o haz el bean @Singleton, que no tiene proxy de cliente.

R4C-QK-006

Causa: un bean protegido por resilience4cata tiene una condición de búsqueda (@LookupIfProperty, @LookupUnlessProperty): una clase anotada, o un bean que implementa una interfaz anotada. El bean que recibe la aplicación es la subclase generada o el wrapper de la interfaz, y Quarkus solo lee las condiciones de búsqueda en la clase original, así que el bean seguiría disponible diga lo que diga la propiedad.

[R4C-QK-006] Beans protected by resilience4cata have lookup conditions (@LookupIfProperty, @LookupUnlessProperty): com.acme.BetaFeature. ...

Corrección: usa una condición de build en su lugar (@IfBuildProperty, @IfBuildProfile y sus formas Unless), que funciona en clases anotadas, o comprueba la propiedad en el código que busca el bean.

R4C-QK-007

Causa: un recurso de Quarkus REST (una clase con @Path, o que implementa una interfaz con @Path) es una clase anotada. Quarkus REST crea las clases de recursos por su cuenta, así que la subclase generada nunca atendería los endpoints y las políticas no se ejecutarían.

[R4C-QK-007] REST resource classes (@Path) have resilience4cata annotations: com.acme.OrdersResource. ...

Corrección: mueve las llamadas protegidas a un bean que el recurso inyecte (una clase anotada, o un bean que implemente una interfaz anotada), o protege la interfaz del cliente REST que el recurso llama.

Pruebas: testkit (R4C-TK)

ManualClock y ResilienceTesting reportan un mal uso en las pruebas con estos códigos.

R4C-TK-001

Causa: ManualClock.advance(…) con una duración negativa (IllegalArgumentException). Un reloj solo avanza.

Corrección: pasa una duración de cero o más. Para empezar una prueba en otro momento, crea el reloj con ese tiempo: new ManualClock(startNanos), o @ResilienceTest(startNanos = …) con la extensión de JUnit.

R4C-TK-002

Causa: ManualClock.advanceUntilIdle() se rindió después de 100.000 tareas seguidas (IllegalStateException): cada tarea que ejecutó programó otra, así que el reloj nunca quedaría libre. Normalmente es un retry que sigue fallando sin límite de tiempo, o un helper de prueba que se reprograma solo.

Corrección: avanza a un tiempo fijo con clock.advance(…), o dale un límite al retry (maxAttempts, maxDuration) para que las tareas terminen.

R4C-TK-003

Causa: ResilienceTesting.elapseWaitInOpen(breaker, clock) sobre un circuit breaker que no está OPEN (IllegalStateException); el mensaje dice en qué estado está.

Corrección: llámalo cuando el breaker ya se abrió: comprueba que los fallos registrados alcancen minimumCalls y failureRateThreshold. Para probar el estado abierto sin fallos, usa breaker.forceOpen().

Pruebas: extensión de JUnit (R4C-JU)

@ResilienceTest (ResilienceExtension) reporta un mal uso con estos códigos; JUnit los muestra como el fallo de la prueba o de la clase.

R4C-JU-001

Causa: un parámetro ManualClock o ResilienceRegistry en un constructor o en un método @BeforeAll (ParameterResolutionException). La extensión crea un reloj y un registry nuevos para cada prueba, así que no hay ninguno antes de que empiece la primera.

Corrección: recíbelos como parámetros del método de prueba o de un método @BeforeEach.

R4C-JU-002

Causa: un método @ConfigureRegistry con otra firma (ExtensionConfigurationException; JUnit 5 lo envuelve en una ParameterResolutionException).

Corrección: que sea un método void no privado con un parámetro ResilienceRegistry.Builder, estático o no: void configure(ResilienceRegistry.Builder builder).

R4C-JU-003

Causa: con @ResilienceTest(failOnPendingTasks = true), una prueba pasó pero dejó tareas en su reloj (AssertionError): un retry, una espera o un timeout async que nunca se ejecutó. El mensaje lista sus plazos.

Corrección: avanza el reloj más allá de ellas (clock.advanceUntilIdle(), o clock.advance(…) lo suficiente), o pon failOnPendingTasks = false si dejarlas es intencional.