Getting started
Add resiliencia-core and resiliencia-patterns, then wrap your first call in a Retry.
There's no framework integration and no runtime magic — every pattern is a plain, immutable object you
configure once and reuse. This guide covers the two modules you need for that: resiliencia-core
(the Resilient contract) and resiliencia-patterns (Retry, Timeout, CircuitBreaker,
Bulkhead, RateLimiter).
Add the dependencies
The project is currently pre-release (1.0.0-beta.1), so these coordinates aren't published to Maven Central
yet — build and install locally from source (mvn install) if you want to depend on them today.
<dependency>
<groupId>io.github.teceli</groupId>
<artifactId>resiliencia-core</artifactId>
<version>1.0.0-beta.1</version>
</dependency>
<dependency>
<groupId>io.github.teceli</groupId>
<artifactId>resiliencia-patterns</artifactId>
<version>1.0.0-beta.1</version>
</dependency>
Both modules have zero external dependencies — resiliencia-patterns only needs resiliencia-core and
SLF4J for logging. Nothing else gets pulled onto your classpath.
Configure a Retry
Retry.create(name) returns an instance configured with sensible defaults — up to 3 attempts, 100ms initial
delay, doubling backoff. Refine it with withX methods; each one returns a new, independently usable
Retry, so the original is never mutated:
import io.github.teceli.resiliencia.patterns.retry.Retry;
var retry = Retry.<String>create("fetch-order")
.withMaxAttempts(3)
.withInitialDelay(100)
.withBackoffMultiplier(2.0)
.withShouldRetry(e -> e instanceof java.io.IOException);
By default, Retry only retries IOException and its subclasses — the assumption is that those are
transient (network errors, timeouts, connection resets), while everything else is a permanent failure.
Pass your own predicate to withShouldRetry to change that.
Wrap a call
Retry implements Resilient<T>, the same interface every pattern implements. Call call(...) to get a
result or an exception, or outcome(...) if you'd rather not deal with exceptions at all:
var order = retry.call(() -> api.fetchOrder(orderId));
If every attempt fails, call throws one of three unchecked exceptions depending on why the loop stopped:
RetryExhaustedException (ran out of attempts or hit the overall deadline), RetryRejectedException (the
exception didn't pass shouldRetry), or RetryInterruptedException (the thread was interrupted during a
backoff wait). All three — like every resiliencia exception — extend ResilientException, so you can catch
that one type if you don't need to distinguish them:
try {
var order = retry.call(() -> api.fetchOrder(orderId));
} catch (ResilientException e) {
log.warn("Could not fetch order {}", orderId, e);
}
Prefer not to use exceptions for control flow? outcome(...) never throws for a recorded failure — it
always returns a Success, Failure, or TimedOut:
var result = retry.outcome(() -> api.fetchOrder(orderId));
switch (result) {
case Outcome.Success<Order>(var value) -> log.info("Got order {}", value);
case Outcome.Failure<Order>(var cause) -> log.warn("Failed after retries", cause);
case Outcome.TimedOut<Order> ignored -> log.warn("Timed out");
}
Next steps
This is enough to retry a single call. From here:
- Composing a Policy — chain
RetrywithCircuitBreakerandTimeoutin one explicit, validated order viaresiliencia-compose. - Testing with ManualClock — drive retries and timeouts deterministically in
tests, with
resiliencia-test'sManualClock, instead of realThread.sleepdelays. - Metrics with Micrometer — wire pattern and Policy events into Micrometer
counters, gauges and timers with
resiliencia-metricsandresiliencia-micrometer.