Skip to content
resilience4cata
ENES

Error reference

Every error that resilience4cata reports starts with a stable code in brackets, for example:

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.

The code tells you which section of this page explains the error. The rest of the message says where (the compiler points at the annotated element; class, method and attribute are named in the text), what is wrong and how to fix it.

Prefix Reported by When
R4C-APT annotation processor placement of the annotations, general problems
R4C-ATR annotation processor attribute values
R4C-CMP annotation processor composed annotations (@ResiliencePolicy bundles)
R4C-ASY annotation processor asynchronous methods
R4C-HDG annotation processor @Hedge
R4C-CLS annotation processor classifier classes and onExhaustedResult
R4C-FB annotation processor @Fallback
R4C-SUB annotation processor annotated classes (generated subclasses)
R4C-RT resilience4cata-core at run time Resilient.of, Resilient.create, Pipeline.Builder
R4C-REG resilience4cata-core at run time *Config builders, ResilienceRegistry and its builder: named configs, instances
R4C-CFG resilience4cata-config at run time external configuration (ResilienceConfig)
R4C-SPR resilience4cata-spring at startup the Spring Boot integration: beans that cannot be protected as declared
R4C-MN resilience4cata-micronaut at startup or bean creation the Micronaut integration: beans that cannot be protected as declared
R4C-QK resilience4cata-quarkus while Quarkus builds the application the Quarkus extension: beans that cannot be protected as declared, or would be protected twice
R4C-TK resilience4cata-testkit in tests ManualClock, ResilienceTesting
R4C-JU resilience4cata-junit in tests the JUnit extension (@ResilienceTest)

Codes are stable: a code is never renumbered or given a new meaning. A new error gets the next free number of its area; an error that is no longer reported keeps its code, marked as retired below.

The wrong and fixed Java examples of the annotation processor's codes are compiled by the build (ErrorCodeTest in the processor module): each wrong example reports its code and each fix compiles without warnings.

Annotation processor: placement (R4C-APT)

R4C-APT-001

Trigger: a resilience annotation on an enum or a record, or on one of its methods. The interface-wrapper style needs an interface, and the subclass style must extend the type, but enums and records are final.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: declare the methods in an interface, implement it with the record, and wrap the implementation with 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

Trigger: a resilience annotation on a static or private method. Calls to such methods cannot be intercepted by a wrapper or a subclass.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: annotate a non-private instance method (abstract or default in an interface; overridable in a class).

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-APT-003

Trigger: the annotated interface is private (a private member of a class). The generated class <Interface>$Resilient is a top-level class and must implement it.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: make the interface package-private or public.

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-APT-004

Trigger: an annotation has an attribute that the processor does not know, for example Unknown attribute @Retry.hedges. This happens when resilience4cata-annotations is newer than resilience4cata-processor.

Fix: use the same version for every resilience4cata artifact, ideally through the 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

Trigger: the processor could not write a generated source file, or the native-image metadata file it writes next to it (META-INF/native-image/dev.resilience4cata.generated/<Interface>$Resilient/reachability-metadata.json in the class output): the compiler's Filer failed, for example because the output directory is not writable, or because another processor or a source file already defines <Interface>$Resilient.

Fix: check the build's generated-sources directory (with Gradle, build/generated/sources/annotationProcessor), make sure no hand-written class is named <Interface>$Resilient, and rebuild after ./gradlew clean.

R4C-APT-006

Trigger: the compiler option -Ar4c.inject has a value other than micronaut; or it is -Ar4c.inject=micronaut but Micronaut's io.micronaut.context.annotation.Replaces is not on the compile class path, so the generated subclass of an annotated class cannot replace the class as a Micronaut bean. The option is read once per compilation; the error has no source position.

Wrong (compiled with -Ar4c.inject=spring):

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: use -Ar4c.inject=micronaut in a Micronaut application, with io.micronaut:micronaut-inject on the compile class path (every Micronaut application has it); remove the option for any other container (Spring Boot needs none: see the Spring Boot guide). See the Micronaut guide.

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

Annotation processor: attribute values (R4C-ATR)

R4C-ATR-001

Trigger: a duration attribute (delay, maxDelay, maxDuration, slowCallDuration, waitInOpen, maxWaitInHalfOpen, maxWait, period, budgetWindow, window, or the value of @Timeout) is not a valid duration.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: use a number followed by ns, us, ms, s, m, h or d ("250ms", "2s"), an ISO-8601 duration ("PT2S"), or "0".

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-ATR-002

Trigger: a duration that must be greater than zero is "0": the value of @Timeout, or @RateLimiter.period.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Timeout;

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

Fix: set a positive duration. To switch the policy off, use enabled = "false" (or configuration) instead.

package com.acme;

import dev.resilience4cata.annotation.Timeout;

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

R4C-ATR-003

Trigger: a count is less than 1: maxAttempts, windowSize, minimumCalls, halfOpenCalls, maxConcurrent, permits, initialLimit, minLimit or maxLimit.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: set it to 1 or more (maxAttempts counts the first call), or leave it out to use the configured value.

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-ATR-004

Trigger: @Retry.multiplier is less than 1, infinite or NaN.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: use a finite number of at least 1 (1 keeps the delay constant).

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-ATR-005

Trigger: @CircuitBreaker.failureRateThreshold or slowCallRateThreshold is not a percentage greater than 0 and at most 100.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.CircuitBreaker;

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

Fix: use a value in (0, 100].

package com.acme;

import dev.resilience4cata.annotation.CircuitBreaker;

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

R4C-ATR-006

Trigger: enabled is neither "" (not set), "true" nor "false".

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: use "true" or "false", or leave it out.

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-ATR-007

Trigger: a retry or hedge budget attribute is out of range: budgetPercent is negative, infinite or NaN, budgetMinRetriesPerSecond (@Retry) or budgetMinHedgesPerSecond (@Hedge) is negative, or budgetWindow is shorter than 1 s or longer than 1 h. (A budgetWindow that is not a duration at all is R4C-ATR-001.)

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: use a percentage of 0 or more, a reserve of 0 or more retries per second, and a window between "1s" and "1h"; or leave the attribute out to inherit it (budgetWindow defaults to 10s).

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

Trigger: @Hedge(maxAttempts = …) is below 2 or above 10. maxAttempts counts every attempt, the first one included, so 1 would never hedge; more than 10 attempts in flight for one call is refused.

Wrong:

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);
}

Fix: use a value from 2 (one hedge, the default) to 10, or remove @Hedge if the call should not be hedged.

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

Trigger: the bounds of @AdaptiveLimit are out of order (minLimit above maxLimit, or initialLimit outside them), or its window is shorter than 10 ms or longer than 1 h. Only the bounds set on the annotation are compared; a bound that comes from a named or external configuration is checked when the registry resolves it (R4C-REG-003).

Wrong:

package com.acme;

import dev.resilience4cata.annotation.AdaptiveLimit;

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

Fix: keep minLimit <= initialLimit <= maxLimit, and a window from "10ms" to "1h".

package com.acme;

import dev.resilience4cata.annotation.AdaptiveLimit;

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

Annotation processor: composed annotations (R4C-CMP)

R4C-CMP-001

Trigger: an annotation type carries a resilience annotation but is not marked with @ResiliencePolicy, so the processor would ignore its policies wherever it is used.

Wrong:

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 {
}

Fix: add @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

Trigger: two composed annotations on the same element set the same attribute to different values, so neither can win.

Wrong:

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 {
}

Fix: keep only one of the bundles, or make them agree, or set the attribute directly on the element, which wins over every bundle.

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 {
}

Annotation processor: asynchronous methods (R4C-ASY)

R4C-ASY-001

Trigger: an annotated method returns a CompletionStage type that is not supported: a raw type, a ? super X argument, or a CompletionStage subtype other than CompletableFuture.

Wrong:

package com.acme;

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

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

Fix: return CompletionStage<T> or CompletableFuture<T>, where T may also be ? or ? 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

Trigger: an annotated method returns a lazy reactive type: a Flow.Publisher or Reactive Streams Publisher (Reactor's Mono and Flux, RxJava's Flowable), Mutiny's Uni or Multi, or RxJava's Single, Maybe, Completable or Observable. Such a method only builds a pipeline that runs later, when it is subscribed, so the policies would protect building it, not the work: a failure would never be retried or timed out.

Wrong:

package com.acme;

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

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

Fix: return CompletionStage<T> or CompletableFuture<T>, converting in the implementation (uni.subscribeAsCompletionStage(), mono.toFuture()), or keep the reactive type and use the reactive library's own retry and timeout operators.

package com.acme;

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

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

Annotation processor: hedges (R4C-HDG)

R4C-HDG-001

Trigger: @Hedge applies to a synchronous method: one that does not return CompletionStage<T> or CompletableFuture<T>. A hedge starts the call again while the first attempt is still running, which needs an asynchronous call. A @Hedge on the type, or in a @ResiliencePolicy bundle, applies to every method, so each synchronous method of the type is reported.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Hedge;

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

Fix: return a CompletableFuture (or CompletionStage), or remove @Hedge from the method. For a @Hedge on the type, annotate the async methods instead of the type.

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

Trigger: @Hedge and @Retry apply to the same method, directly, on the type, or through @ResiliencePolicy bundles. They take the same place in the order of policies, and a retry around a hedge would multiply the attempts.

Wrong:

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);
}

Fix: keep one. A hedge already starts a new attempt at once when one fails with a failure in hedgeOn, so maxAttempts on the hedge covers the simple retry.

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);
}

Annotation processor: policy order (R4C-ORD)

R4C-ORD-001

Trigger: @PolicyOrder lists a layer more than once (on the method, on its type or in a bundle). Each layer has one place in the order.

Wrong:

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);
}

Fix: list each layer once, outermost first.

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);
}

Annotation processor: classifiers (R4C-CLS)

The generated class creates one instance of each classifier class per method, so the class must be a concrete, non-generic class with an accessible no-argument constructor, implementing Classifier<T> for the method's result type.

R4C-CLS-001

Trigger: classifier is set, but dev.resilience4cata.Classifier is not on the compile classpath.

Wrong: compiling this without resilience4cata-core on the 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;
        }
    }
}

Fix: add the core dependency:

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

R4C-CLS-002

Trigger: classifier names an interface, an annotation or an enum instead of a class.

Wrong:

package com.acme;

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

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

Fix: name a class that implements 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

Trigger: the classifier class is abstract, so it cannot be instantiated.

Wrong:

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> {
    }
}

Fix: use a concrete class.

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

Trigger: the classifier class declares type parameters; the generated code could only create it with an unchecked raw type.

Wrong:

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;
        }
    }
}

Fix: implement Classifier<T> with a concrete T (or a supertype, such as 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

Trigger: the classifier class (or a class it is nested in) is private, or package-private in another package, so the generated class, in the interface's package, cannot create it.

Wrong:

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;
        }
    }
}

Fix: make it public, or package-private in the interface's package.

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

Trigger: the classifier is an inner (non-static nested) class, which needs an instance of its enclosing class.

Wrong:

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;
        }
    }
}

Fix: make it 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

Trigger: the classifier has no no-argument constructor that the generated class can call.

Wrong:

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;
        }
    }
}

Fix: add a public (or, in the interface's package, package-private) no-argument constructor, or remove the private one.

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

Trigger: the classifier class does not implement dev.resilience4cata.Classifier.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: name a class that implements 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

Trigger: the classifier implements the raw type Classifier, so its result type cannot be checked.

Wrong:

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;
        }
    }
}

Fix: implement Classifier<T> with the method's result type, as the message suggests.

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

Trigger: the classifier implements Classifier<X>, but the method's result (the value of the stage, for an asynchronous method) is not assignable to X.

Wrong:

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;
        }
    }
}

Fix: implement Classifier for the method's result type or a supertype of it.

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

Trigger: @Retry sets both classifier and retryOn, or @CircuitBreaker sets both classifier and recordOn. The classifier decides which exceptions are failures, so the list would be ignored.

Wrong:

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;
        }
    }
}

Fix: remove retryOn (or recordOn) and return Outcome.FAILURE for those exceptions from the classifier. abortOn and ignoreOn still apply before the classifier.

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

Trigger: @Retry sets onExhaustedResult without a classifier. Only a classifier can reject a result, so the setting would never apply.

Wrong:

package com.acme;

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

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

Fix: add a classifier that rejects the results to retry, or remove 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;
        }
    }
}

Annotation processor: fallbacks (R4C-FB)

A @Fallback names either a handler class, @Fallback(MyHandler.class), or sibling methods, @Fallback(method = "name"). A handler class must meet the same rules as a classifier: concrete, non-generic, accessible, with a no-argument constructor, implementing FallbackHandler<T> for the method's result type.

R4C-FB-001

Trigger: @Fallback sets neither a handler class nor a method name, or sets both.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

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

Fix: set exactly one of them.

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

Trigger: @Fallback(method = "...") names a method that does not exist in the interface (or class). The message suggests the closest existing name.

Wrong:

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";
    }
}

Fix: correct the name, or add the method. It takes the same parameters as the annotated method, optionally followed by the exception, and returns the same type.

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

Trigger: methods with the fallback's name exist, but none can be used. The message lists each candidate and why it was rejected: private (the generated class cannot call it), wrong return type, a checked exception the annotated method does not declare, different type parameters, or an exception parameter that is not a class. Candidates with other parameters are not listed.

Wrong:

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";
    }
}

Fix: in an interface, use a default or static method; in a class, a non-private one. Match the signature that the message shows.

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

Trigger: @Fallback names a handler class, but dev.resilience4cata.FallbackHandler is not on the compile classpath.

Wrong: compiling this without resilience4cata-core on the 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";
        }
    }
}

Fix: add the core dependency:

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

R4C-FB-005

Trigger: the handler named by @Fallback is an interface, an annotation or an enum instead of a class.

Wrong:

package com.acme;

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

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

Fix: name a class that implements 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

Trigger: the handler class is abstract.

Wrong:

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> {
    }
}

Fix: use a concrete class.

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

Trigger: the handler class declares type parameters.

Wrong:

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;
        }
    }
}

Fix: implement FallbackHandler<T> with a concrete T. For a generic method, use a sibling method instead (see 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

Trigger: the handler class (or a class it is nested in) is private, or package-private in another package, so the generated class cannot create it.

Wrong:

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";
        }
    }
}

Fix: make it public, or package-private in the interface's package.

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

Trigger: the handler is an inner (non-static nested) class.

Wrong:

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";
        }
    }
}

Fix: make it 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

Trigger: the handler has no no-argument constructor that the generated class can call.

Wrong:

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";
        }
    }
}

Fix: add a public (or, in the interface's package, package-private) no-argument constructor, or remove the private one.

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

Trigger: the handler class does not implement dev.resilience4cata.FallbackHandler.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Fallback;

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

Fix: name a class that implements 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

Trigger: the handler implements the raw type FallbackHandler.

Wrong:

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";
        }
    }
}

Fix: implement FallbackHandler<T> with the method's result type, as the message suggests.

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

Trigger: the handler implements FallbackHandler<X>, but X is not assignable to the method's return type. For an asynchronous method returning a stage of T, X may be T or CompletionStage<T>.

Wrong:

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;
        }
    }
}

Fix: implement FallbackHandler for the method's return type (boxed, for a primitive).

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

Trigger: a handler class on a generic method whose return type depends on the method's type parameters. A handler implements FallbackHandler<X> for one X, which cannot fit every instantiation without an unchecked cast.

Wrong:

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;
        }
    }
}

Fix: use a sibling fallback method that declares the same type parameters.

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;
    }
}

Annotation processor: annotated classes (R4C-SUB)

An annotated class gets a generated subclass, <Class>$Resilient, which overrides the annotated methods and is created with Resilient.create(...). The class must therefore be extensible, and the annotated methods overridable.

R4C-SUB-001

Trigger: the annotated class is final.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: remove final, or move the annotations to an interface and wrap the implementation with Resilient.of(...).

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-SUB-002

Trigger: the annotated class is sealed, so the generated subclass is not a permitted subclass.

Wrong:

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 {
}

Fix: make it non-sealed (or a regular class), or use an interface and 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

Trigger: the annotated class is abstract. The generated subclass must be instantiable and calls super for each method.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: annotate a concrete subclass, or an interface.

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-SUB-004

Trigger: the annotated class, or a class it is nested in, is private.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: make it package-private, protected or public.

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

Trigger: the annotated class is an inner (non-static nested) class: its generated subclass would need an instance of the enclosing class.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: make it a static nested class.

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

Trigger: every constructor of the annotated class is private, so the generated subclass cannot call any.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    private PriceService() {
    }

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

Fix: make at least one constructor package-private, protected or public.

package com.acme;

import dev.resilience4cata.annotation.Retry;

public class PriceService {
    protected PriceService() {
    }

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

R4C-SUB-007

Trigger: an annotated method (in the class, a superclass or an interface it implements) is final, so the subclass cannot override it and its annotations would be ignored.

Wrong:

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

Fix: remove final, or remove the annotations.

package com.acme;

import dev.resilience4cata.annotation.Retry;

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

R4C-SUB-008

Trigger: an annotated method is package-private in a superclass from another package. The generated subclass, in the annotated class's package, cannot override it, so its annotations would be ignored.

Wrong:

// 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";
    }
}

Fix: make the method protected or public.

// 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";
    }
}

Runtime: Resilient and Pipeline (R4C-RT)

These are thrown by resilience4cata-core when a service is wrapped or a pipeline is built. The exception types are documented on each method; the code is at the start of the message.

R4C-RT-001

Trigger: Resilient.of(type, ...) with a type that is a class, not an interface (IllegalArgumentException).

Wrong: Resilient.of(PriceService.class, new PriceService(), registry) where PriceService is an annotated class.

Fix: for an annotated class, use Resilient.create(PriceService.class, registry); Resilient.of takes the annotated interface.

R4C-RT-002

Trigger: the object passed to Resilient.of does not implement the interface (IllegalArgumentException). It can only happen through raw types or unchecked casts.

Fix: pass an implementation of the interface.

R4C-RT-003

Trigger: the generated class <type>$Resilient was not found (IllegalStateException): the type has no resilience annotation, or the annotation processor did not run when it was compiled.

Wrong: a build that only adds the annotations:

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

Fix: add the processor, which is mandatory (there is no reflection fallback):

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

In a GraalVM native image, the same error means the processor ran but the image cannot reach the class by reflection: the reachability metadata it writes next to the class files, META-INF/native-image/dev.resilience4cata.generated/<type>$Resilient/reachability-metadata.json, was not on the image build's class path, or a build step (shading, a custom resource filter) stripped it. The message then ends with a sentence that names that file. Keep the processor's output, metadata included, in the jar or directory that native-image reads; see Native image.

R4C-RT-004

Trigger: the generated class does not have the constructor that this version of resilience4cata-core expects, or a generated subclass does not extend the annotated class (IllegalStateException). It was generated by an incompatible version of the processor.

Fix: use the same version for resilience4cata-core and resilience4cata-processor (the BOM keeps them aligned), then rebuild from clean.

R4C-RT-005

Trigger: resilience4cata-core cannot access the generated class (IllegalStateException), usually because the application is a named module that does not export the package to core.

Fix: add to the application's module-info.java:

exports com.acme to dev.resilience4cata.core;

R4C-RT-006

Trigger: the generated class's constructor failed with a checked exception, or could not be instantiated (IllegalStateException, with the failure as its cause). Unchecked exceptions from the constructor, such as an invalid configuration, are rethrown as they are.

Fix: look at the cause; it usually comes from a classifier or fallback handler constructor.

R4C-RT-007

Trigger: Resilient.create(type, ...) with an interface (IllegalArgumentException).

Fix: wrap an implementation instead: Resilient.of(PriceClient.class, implementation, registry).

R4C-RT-008

Trigger: Resilient.create(type, ...) with a type that cannot have a generated subclass: final, abstract, local, anonymous, an enum, a record, an array or a primitive (IllegalArgumentException). The processor reports most of these at compile time (R4C-SUB-001 and following).

Fix: annotate a top-level or static nested class that is neither final nor abstract, or an interface and use Resilient.of(...).

R4C-RT-009

Trigger: no constructor of the annotated class accepts the arguments passed to Resilient.create (IllegalArgumentException). The message lists the constructors.

Fix: pass arguments that match one constructor, or call the generated constructor directly, which the compiler checks: new PriceService$Resilient(registry, ...).

R4C-RT-010

Trigger: more than one constructor accepts the arguments passed to Resilient.create, for example a single null (IllegalArgumentException).

Fix: call the generated constructor directly, with a cast to pick the overload: new PriceService$Resilient(registry, (String) null).

R4C-RT-011

Trigger: Pipeline.builder(registry, id) with a blank id (IllegalArgumentException).

Fix: pass the id that names the pipeline's policy instances, for example "payments".

R4C-RT-012

Trigger: a Pipeline.Builder part is set twice: a policy, a classifier, onExhaustedResult or a fallback (IllegalStateException).

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

Fix: set each part once per builder; pass the overrides to the one call: retry("five", b -> b.maxAttempts(3)).

R4C-RT-013

Trigger: fallbackApplyOn or fallbackSkipOn is set, but the pipeline has no fallback (IllegalStateException from build()).

Fix: add a fallback (fallback, fallbackHandler, fallbackAsync), or remove the selection.

R4C-RT-014

Trigger: classifier(...) is set, but the pipeline has neither a retry nor a circuit breaker (IllegalStateException from build()).

Fix: add the policy the classifier is meant for, or remove the classifier.

R4C-RT-015

Trigger: retryClassifier(...) is set, but the pipeline has no retry (IllegalStateException from build()).

Fix: add a retry, or remove the retry classifier.

R4C-RT-016

Trigger: onExhaustedResult(...) is set, but the pipeline has no retry with a classifier (IllegalStateException from build()). Only a classifier can reject a result. The annotation equivalent is R4C-CLS-012.

Fix: add a retry and a classifier (classifier or retryClassifier), or remove onExhaustedResult.

R4C-RT-017

Trigger: circuitBreakerClassifier(...) is set, but the pipeline has no circuit breaker (IllegalStateException from build()).

Fix: add a circuit breaker, or remove the circuit breaker classifier.

R4C-RT-018

Trigger: execute(...) or run(...) is called on a pipeline that has a hedge (IllegalStateException). A hedge starts the call again while the first attempt is still running, which needs a call that returns a CompletionStage.

Fix: call executeAsync(() -> client.callAsync(...)), or remove .hedge(...) from the pipeline.

R4C-RT-019

Trigger: the pipeline has both .retry(...) and .hedge(...) (IllegalStateException from build()).

Fix: keep one: a hedge already starts a new attempt when one fails (hedgeOn), and a retry around it would multiply the attempts. This is the runtime twin of R4C-HDG-002.

Runtime: registry and policy configuration (R4C-REG)

These are thrown by resilience4cata-core when a policy configuration is built (RetryConfig.builder() and the other *Config builders), when a ResilienceRegistry is built, and when the registry creates or looks up a policy instance: while a service is wrapped (Resilient.of, Resilient.create) or a pipeline is built (Pipeline.Builder.build(), which passes them on unchanged). The exception types are documented on each method; the code is at the start of the message. External configuration (ResilienceConfig) validates its values before they reach the registry and reports them as R4C-CFG-006, with the keys that set them.

R4C-REG-001

Trigger: a *Config builder rejected a value (IllegalArgumentException from build()), for example a maxAttempts below 1, a negative duration, a maxDelay shorter than the delay, or a rate threshold outside (0, 100]. The message names the attribute and the value.

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

Fix: use a value in the range the message gives: RetryConfig.builder().maxAttempts(1).build().

R4C-REG-002

Trigger: ResilienceRegistry.Builder.build() found an invalid default or named configuration (IllegalArgumentException, with the R4C-REG-001 error as its cause). The message names the config (or the defaults) and where it was set.

Wrong:

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

Fix: make the values of that config valid together. A named config starts from the defaults (retryDefaults(...)), then applies retry("slow", ...), then retryOverlay("slow", ...) (configs.slow.retry.* in external configuration):

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

R4C-REG-003

Trigger: the configuration of one policy instance is invalid once everything that applies to it is combined (IllegalArgumentException, with the R4C-REG-001 error as its cause): its named config (or the defaults), then the values set in its annotation or pipeline step, then its per-instance overrides (retryInstance(id, ...), instances.<id>.* in external configuration). It is thrown when the instance is created: by Resilient.of/Resilient.create or by Pipeline.Builder.build().

Wrong: a per-instance override that does not fit the instance's own values:

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();

Fix: change the override, or the value it collides with, so that the combination is valid, for example retryInstance("payments", r -> r.delay(Duration.ofSeconds(10))).

R4C-REG-004

Trigger: config = "name" (in an annotation or a pipeline step), or retryConfig("name") and the other *Config(String) lookups, names a config that the registry does not have (IllegalArgumentException). The message lists the known configs and suggests the closest one ("Did you mean …?").

Wrong: @Retry(config = "fiev") with a registry that only registers retry("five", ...).

Fix: correct the name, or register the config: ResilienceRegistry.builder().retry("fiev", r -> ...), or configs.fiev.retry.* in external configuration.

R4C-REG-005

Trigger: the same named config is registered twice on one builder, for example two calls to retry("five", ...) (IllegalArgumentException).

Fix: register it once. To change some of its attributes elsewhere, use the overlay, which applies on top of it: retryOverlay("five", r -> r.maxAttempts(3)).

R4C-REG-006

Trigger: a named config or overlay is registered with the name "" or "default", for example retry("default", ...) (IllegalArgumentException). Both names are reserved: they mean the defaults.

Fix: change the defaults with retryDefaults(...) (defaults.retry.* in external configuration), or give the config another name.

R4C-REG-007

Trigger: retryInstance("", ...) or another *Instance(...) call with an empty instance id (IllegalArgumentException).

Fix: pass the id of the instance: the name set in its annotation, the pipeline id, or the derived id <interface>#<method>(<parameter types>), for example com.acme.PriceClient#price(java.lang.String).

R4C-REG-008

Trigger: two call sites share one retry, circuit breaker, rate limiter or bulkhead instance, through the same name in their annotations or the same pipeline id, but resolve to different configurations (IllegalStateException). The message shows both configurations. Instances with the same name are shared, so they must agree.

Wrong:

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

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

Fix: give the call sites different names if they should be independent, or make their configuration identical (for example, the same config = "..." and no differing attributes) if they should share one instance.

R4C-REG-009

Trigger: registry.circuitBreaker(id), registry.rateLimiter(id) or registry.bulkhead(id) with an id that no instance of this registry has (IllegalArgumentException). The message lists the known ids and suggests the closest one. Instances are created when their interface is wrapped (Resilient.of), their class is created (Resilient.create) or their pipeline is built, so looking one up earlier also fails.

Fix: use the name set in the annotation (or the pipeline id), or the derived id <interface>#<method>(<parameter types>), and look it up after the service or pipeline has been created.

Runtime: external configuration (R4C-CFG)

ResilienceConfig.build() reports every problem at once in one ConfigException; each line starts with its code and names the key and its source (file and line, environment variable or system property).

R4C-CFG-001

Trigger: an unknown, malformed or incomplete key: unknown policy or attribute, empty segment, a key that stops early. The message suggests the closest valid key.

Wrong:

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

Fix: use one of the key forms the message lists: defaults.<policy>.<attribute>, configs.<name>.<policy>.<attribute>, configs.<name>.extends, instances.<id>.<policy>.<attribute>, profiles.<profile>.… or profiles.active.

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

R4C-CFG-002

Trigger: a value that cannot be parsed for its attribute: a number, boolean, enum constant or duration.

Wrong:

defaults.retry.delay=fast

Fix: use a value of the attribute's type; durations use the same grammar as annotations ("250ms", "2s", "PT2S", "0").

defaults.retry.delay=250ms

R4C-CFG-003

Trigger: an exception list (retry-on, abort-on, record-on, ignore-on) names a class the class loader cannot find, or a class that is not a Throwable.

Wrong:

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

Fix: use fully qualified names of exception classes visible to the application's class loader.

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

R4C-CFG-004

Trigger: an invalid config or profile name, or a reserved one (default for a config, active for a profile). Names use lowercase letters and digits separated by single hyphens, so that they map to environment variables.

Wrong:

profiles.active=Test

Fix: rename it, for example test.

profiles.active=test

R4C-CFG-005

Trigger: the same key is set twice in one source (for example twice in one file, or under two spellings such as max-attempts and maxAttempts).

Fix: keep one of them. Different sources may set the same key; the precedence order decides.

R4C-CFG-006

Trigger: the values are valid one by one but not together, for example a max-delay shorter than the delay, or max-attempts=0 (the config builder rejected them). The message names the keys involved.

Wrong:

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

Fix: make the values consistent, as the message describes.

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

R4C-CFG-007

Trigger: configs.<name>.extends names a config that is not defined in external configuration, or configs extend each other in a cycle.

Wrong:

configs.batch.extends=slwo

Fix: extend an existing config (the message suggests the closest name and lists the known ones) and break any cycle.

R4C-CFG-008

Trigger: reserved; not reported by the current version.

R4C-CFG-009

Trigger: a configuration source cannot be read: a classpath resource or file that does not exist, or an I/O error.

Fix: check the path, or do not add the source when it is optional.

R4C-CFG-010

Trigger: an environment variable matches more than one key, because _ stands for both . and -.

Fix: set that key in a properties file or as a system property instead.

R4C-CFG-011

Trigger: retired; no longer reported. A named config set both in code and externally is now merged, the external values winning.

R4C-CFG-012

Trigger: a malformed instance id in an instances. key: an id that contains . but is not quoted, an empty id, an unclosed or misplaced ", quotes around anything but the id, or an id that starts or ends with whitespace.

Wrong:

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

Fix: quote the instance id.

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

R4C-CFG-013

Trigger: config.verifyInstances(registry) found an instances. override that matches no instance of that policy in the registry (reported by verifyInstances, not build(), because instances exist only once services are wrapped). The message suggests the closest id.

Fix: use an existing instance id: the policy's name, or <interface>#<method>(<parameter types>) when it has none.

R4C-CFG-014

Trigger: config.verifyInstances(registry) found an instances. override of an attribute that the instance's annotation sets with locked = true (or that a pipeline built with .locked() sets on its step). The registry kept the annotation's value and ignored the override; the message shows the configuration the instance uses.

Fix: remove the key, or remove locked = true from the annotation if operators should be able to change that value. See Locking an instance.

Runtime: Spring Boot integration (R4C-SPR)

resilience4cata-spring checks the beans at startup and fails it with these codes when a bean would silently lose its protection. Spring Boot prints the message as the Description and the fix as the Action of its "APPLICATION FAILED TO START" report. See the Spring Boot guide.

R4C-SPR-001

Trigger: a bean is an instance of an annotated class C but not of the generated C$Resilient, so its policies would never run: a @Bean method returned new C(...), the bean was registered with a supplier, or it is a subclass of C that has no annotations of its own (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. ...

Fix: register the class itself (component scanning, @Import), so that Spring instantiates C$Resilient; or return new C$Resilient(registry, ...) from the @Bean method, with the ResilienceRegistry injected as a parameter; or annotate the subclass. To leave the bean unprotected on purpose, list its name in resilience4cata.spring.exclude.

R4C-SPR-002

Trigger: a @Configuration class with @Bean methods also has resilience4cata annotations. Spring instantiates its own CGLIB subclass of such a class, so the generated subclass cannot be used.

Fix: move the annotated methods to a @Component or @Service class, or declare the configuration with @Configuration(proxyBeanMethods = false), which Spring does not subclass.

R4C-SPR-003

Trigger: a bean implements two or more annotated interfaces, none of which extends all the others (BeanCreationException). The generated wrapper of an interface implements that interface only, so the bean could no longer be injected by the other ones.

Fix: annotate the class instead (its generated subclass keeps every interface and is still the bean's type), or declare one interface that extends the others and implement that one. To wrap the bean yourself, list it in resilience4cata.spring.exclude and define a @Bean that returns Resilient.of(...).

R4C-SPR-004

Trigger: an annotated class has a method with @Retry or @Hedge in effect (on the method, the class, a superclass, an interface or a bundle) that is also transactional: Spring's or Jakarta's @Transactional with a propagation that joins or starts a transaction (REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NESTED) (BeanCreationException). The transaction proxy goes around the generated subclass, so every attempt would run in the same transaction, and an attempt that fails and marks it rollback-only makes the commit of a later successful attempt fail with UnexpectedRollbackException.

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

Fix: declare the policy on an interface the bean implements instead (its wrapper goes outside the transaction proxy: one transaction per attempt); or move @Transactional to another bean that the retried method calls; or, if one shared transaction for every attempt is intended, set resilience4cata.spring.allow-retry-inside-transaction=true. NOT_SUPPORTED and NEVER are not reported.

Runtime: Micronaut integration (R4C-MN)

resilience4cata-micronaut checks the beans and fails with these codes when a bean would silently lose its protection. The @ResilientBean mistakes (001 to 003) stop the startup: every bean definition annotated with @ResilientBean is checked when the context starts, before any is created. The others are reported when the bean is created, which is at startup for @Context beans and on first use for the others (Micronaut creates beans lazily), as a ResilienceBeanException inside Micronaut's BeanInstantiationException. See the Micronaut guide.

R4C-MN-001

Trigger: a bean annotated @ResilientBean(I.class) is exposed as other types than I, for example because it also declares @Bean(typed = {I.class, Impl.class}). @ResilientBean limits the bean's types to I, because the generated wrapper that replaces the bean implements I only: injecting it as another type would fail or bypass the policies.

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

Fix: remove @Bean(typed = ...) (or @Type) from the bean and inject the interface.

R4C-MN-002

Trigger: @ResilientBean(X.class) names a type that is not an interface, or an interface that the bean does not implement, so no generated wrapper can protect it.

Fix: name the annotated interface that the bean implements. For an annotated class, remove @ResilientBean: compile with -Ar4c.inject=micronaut so that Micronaut creates its generated subclass, or return new C$Resilient(registry, ...) from a @Factory method (annotated classes).

R4C-MN-003

Trigger: a bean annotated @ResilientBean has a scope that Micronaut proxies (@Refreshable, @RequestScope, @ThreadLocal, any @ScopedProxy). Such a proxy looks up its target by the bean's class, which the generated wrapper is not.

Fix: make the bean @Singleton, @Prototype or @Context, or keep the scoped bean unprotected and inject it into a singleton that implements the annotated interface.

R4C-MN-004

Trigger: a bean is an instance of an annotated class C but not of the generated C$Resilient, so its policies would never run: Micronaut created C itself (it has a scope annotation or an @Inject constructor, and the code was compiled without -Ar4c.inject=micronaut), or a @Factory method returned new C(...). Also reported when Micronaut creates C$Resilient from its own definition although it was compiled without the option: its constructor repeats C's @Inject, so it is a bean, but without C's scope and qualifiers (a @Singleton would become a new instance per injection).

[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. ...

Fix: compile with -Ar4c.inject=micronaut, so that Micronaut creates C$Resilient in place of C; or remove the scope annotation (and @Inject) from C and return new C$Resilient(registry, ...) from a @Factory method typed C. To leave the bean unprotected on purpose, list the class's name in resilience4cata.micronaut.exclude.

R4C-MN-005

Trigger: an annotated class compiled with -Ar4c.inject=micronaut has a method with @Retry or @Hedge in effect that is also @Transactional (Micronaut's, or Jakarta's mapped onto it) with a propagation that joins or starts a transaction (REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NESTED). The generated subclass repeats @Transactional, so Micronaut's transaction proxy goes around it: every attempt would run in the same transaction, and an attempt that fails and marks it rollback-only makes the commit of a later successful attempt fail.

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

Fix: declare the policy on an interface that the bean implements and annotate the bean @ResilientBean (the wrapper goes outside the transaction proxy: one transaction per attempt); or move @Transactional to another bean that the retried method calls; or, if one shared transaction for every attempt is intended, set resilience4cata.micronaut.allow-retry-inside-transaction=true. NOT_SUPPORTED and NEVER are not reported. The check reads the generated class file, so a native image skips it.

R4C-MN-006

Trigger: a switch of the Micronaut integration has an invalid value, for example resilience4cata.micronaut.verify-instances: strict. The context fails to start.

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

Fix: use one of the values the message lists.

Build: Quarkus extension (R4C-QK)

resilience4cata-quarkus checks the application while Quarkus builds it (quarkusBuild, dev mode, every @QuarkusTest) and fails the build with these codes when a bean would not be protected as declared, or would be protected twice. Quarkus prints them among its "deployment problems". See the Quarkus guide.

R4C-QK-001

Trigger: a method protected by resilience4cata also carries MicroProfile or SmallRye Fault Tolerance annotations (org.eclipse.microprofile.faulttolerance.*, io.smallrye.faulttolerance.api.*), on the method or on its class; for an annotated interface, also on the implementing class's method. The simple names are the same (@Retry, @CircuitBreaker, @Timeout, @Bulkhead, @Fallback), so this is usually a wrong import. With quarkus-smallrye-fault-tolerance installed both libraries would apply, so the build fails; without it the Fault Tolerance annotations do nothing, and the same message is logged as a warning.

[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. ...

Fix: keep one library per method: remove the other annotation, or fix the import. See SmallRye Fault Tolerance vs resilience4cata.

R4C-QK-002

Trigger: an annotated class has a method with @Retry or @Hedge in effect that is also @Transactional (Jakarta Transactions, with Narayana) with a type that joins or starts a transaction (REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS). The transaction interceptor goes around the generated subclass, so every attempt would run in the same transaction, and an attempt that fails and marks it rollback-only makes the commit of a later successful attempt fail.

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

Fix: declare the policy on an interface the bean implements instead (its wrapper goes outside the transaction interceptor: one transaction per attempt); or move @Transactional to another bean that the retried method calls; or, if one shared transaction for every attempt is intended, set quarkus.resilience4cata.allow-retry-inside-transaction=true. NOT_SUPPORTED and NEVER are not reported.

R4C-QK-003

Trigger: a bean implements two or more annotated interfaces, none of which extends all the others. The generated wrapper of an interface implements that interface only, so the bean can be injected through one wrapper only.

Fix: declare one interface that extends the others and implement that one, or annotate the class instead. To wrap the bean yourself, list its class in quarkus.resilience4cata.beans.exclude and return Resilient.of(...) from a producer method.

R4C-QK-004

Trigger: an annotated class declares producer methods or fields (@Produces). The generated subclass replaces the class as the bean, and CDI producers are not inherited, so they would disappear.

Fix: move the producers to another bean, or list the class in quarkus.resilience4cata.beans.exclude.

R4C-QK-005

Trigger: an annotated class is a normal-scoped bean (@ApplicationScoped, @RequestScoped, @SessionScoped) and has no non-private constructor without parameters. The client proxy that Quarkus puts in front of the generated subclass needs one, and Quarkus can add it to the subclass only if the class has one.

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

Fix: add a constructor without parameters (package-private is enough: only the proxy uses it), or use field injection instead of constructor injection, or make the bean @Singleton, which has no client proxy.

R4C-QK-006

Trigger: a bean protected by resilience4cata has a lookup condition (@LookupIfProperty, @LookupUnlessProperty): an annotated class, or a bean implementing an annotated interface. The bean the application gets is the generated subclass or the interface wrapper, and Quarkus reads lookup conditions only on the original class, so the bean would stay resolvable whatever the property says.

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

Fix: use a build-time condition instead (@IfBuildProperty, @IfBuildProfile and their Unless forms), which works on annotated classes, or check the property in the code that looks the bean up.

R4C-QK-007

Trigger: a Quarkus REST resource (a class with @Path, or one implementing an interface with @Path) is an annotated class. Quarkus REST creates resource classes itself, so the generated subclass would never serve the endpoints and the policies would not run.

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

Fix: move the protected calls to a bean that the resource injects (an annotated class, or a bean implementing an annotated interface), or protect the REST client interface the resource calls.

Tests: testkit (R4C-TK)

ManualClock and ResilienceTesting report misuse in tests with these codes.

R4C-TK-001

Trigger: ManualClock.advance(…) with a negative duration (IllegalArgumentException). A clock only moves forward.

Fix: pass a duration of zero or more. To start a test at another time, create the clock with that time: new ManualClock(startNanos), or @ResilienceTest(startNanos = …) with the JUnit extension.

R4C-TK-002

Trigger: ManualClock.advanceUntilIdle() gave up after 100,000 tasks in a row (IllegalStateException): every task it ran scheduled another one, so the clock would never be idle. Usually a retry that keeps failing with no limit in time, or a test helper that reschedules itself.

Fix: advance to a fixed time with clock.advance(…) instead, or give the retry a limit (maxAttempts, maxDuration) so that the tasks end.

R4C-TK-003

Trigger: ResilienceTesting.elapseWaitInOpen(breaker, clock) on a circuit breaker that is not OPEN (IllegalStateException); the message says which state it is in.

Fix: call it once the breaker opened: check that the failures recorded so far reach minimumCalls and failureRateThreshold. To test the open state without failures, use breaker.forceOpen().

Tests: JUnit extension (R4C-JU)

@ResilienceTest (ResilienceExtension) reports misuse with these codes; JUnit shows them as the failure of the test or class.

R4C-JU-001

Trigger: a ManualClock or ResilienceRegistry parameter on a constructor or a @BeforeAll method (ParameterResolutionException). The extension creates a new clock and registry for each test, so there is none before the first test starts.

Fix: take them as parameters of the test method or of a @BeforeEach method.

R4C-JU-002

Trigger: a @ConfigureRegistry method with another signature (ExtensionConfigurationException; JUnit 5 wraps it in a ParameterResolutionException).

Fix: make it a non-private void method with one ResilienceRegistry.Builder parameter, static or not: void configure(ResilienceRegistry.Builder builder).

R4C-JU-003

Trigger: with @ResilienceTest(failOnPendingTasks = true), a test passed but left tasks on its clock (AssertionError): an async retry, wait or timeout that never ran. The message lists their delays.

Fix: advance the clock past them (clock.advanceUntilIdle(), or clock.advance(…) far enough), or set failOnPendingTasks = false if leaving them is intended.