Kora AOP Resilient
Kora sub-skill — obey the kora-v1 meta rules on every task: R0 ensure
.kora-agent/docs+examples are cloned · R1 read this sub-skill before writing code · R2 Kora APIs only — no Spring/Micronaut/Quarkus, no invented annotations or config keys · R3 journal any incorrect Kora usage. Add comments/Javadoc only if asked.
Compile-time AOP annotations for fault tolerance: @Retry, @CircuitBreaker, @Timeout, @Fallback. They are generated into $<Class>__AopProxy classes at build time — no reflection. Provided by the resilient-kora module via ResilientModule.
Quick Navigation:
- Quick Start — get running in four steps
- Combined Pattern — stack all annotations on one method
- References — per-aspect deep dives
Quick Start
1. Add the dependency and annotation processor
dependencies {
// All Kora artifacts inherit their version from the kora-parent BOM — never pin them individually.
annotationProcessor "ru.tinkoff.kora:annotation-processors" // mandatory: generates the AOP proxies
implementation "ru.tinkoff.kora:resilient-kora"
}
Kotlin uses
ksp "ru.tinkoff.kora:symbol-processors"instead ofannotationProcessor.
2. Enable ResilientModule
@KoraApp
public interface Application extends
HoconConfigModule,
LogbackModule,
ResilientModule { } // enables the resilience aspects
3. Apply Resilience Annotations
Each annotation lives in its own package:
| Annotation | Import |
|---|---|
@CircuitBreaker |
ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker |
@Retry |
ru.tinkoff.kora.resilient.retry.annotation.Retry |
@Timeout |
ru.tinkoff.kora.resilient.timeout.annotation.Timeout |
@Fallback |
ru.tinkoff.kora.resilient.fallback.annotation.Fallback |
@Component
public class PaymentService { // MUST be non-final (Java) / open (Kotlin) for aspects to apply
@Fallback(value = "payment.process", method = "processFallback(request)")
@CircuitBreaker("payment.process")
@Retry("payment.process")
@Timeout("payment.process")
public PaymentResult process(PaymentRequest request) {
return paymentGateway.charge(request);
}
protected PaymentResult processFallback(PaymentRequest request) {
return PaymentResult.pendingManualReview();
}
}
The annotation value is a config key, not a shared identifier — @Retry("payment.process") reads resilient.retry."payment.process". Different aspects on the same method may use independent keys.
4. Configure Resilience
resilient {
timeout {
default { duration = "1s" }
"payment.process" { duration = "5s" }
}
retry {
default {
delay = "100ms"
attempts = 3
delayStep = "100ms"
}
}
circuitbreaker {
default {
slidingWindowSize = 100
minimumRequiredCalls = 10
failureRateThreshold = 50
waitDurationInOpenState = "30s"
permittedCallsInHalfOpenState = 5
}
}
fallback {
default { enabled = true }
}
}
Important: Use
minimumRequiredCalls(NOTminimumNumberOfCalls) — Kora-specific key.
Combined Resilience Pattern
Recommended order (outer → inner):
@Component
public class PaymentClient {
// Order (outer → inner):
// 1. @Fallback — degraded response if everything fails
// 2. @CircuitBreaker — fail fast if repeatedly failing
// 3. @Retry — retry transient failures
// 4. @Timeout — bound each attempt
@Fallback(value = "payment.charge", method = "chargeFallback(request)")
@CircuitBreaker("payment.charge")
@Retry("payment.charge")
@Timeout("payment.charge")
public PaymentResult charge(PaymentRequest request) {
return httpClient.post("/payments", request);
}
protected PaymentResult chargeFallback(PaymentRequest request) {
return PaymentResult.pendingManualReview();
}
}
Execution flow:
@Timeoutbounds the actual HTTP call@Retryrepeats on transient failures (up to N attempts)@CircuitBreakeropens if failures exceed threshold@Fallbackreturns degraded response if circuit is open or all retries fail
References
Detailed guides with configuration options, patterns, and examples:
| Reference | Description |
|---|---|
| retry-reference.md | @Retry, predicates, backoff patterns, wait time calculation |
| circuit-breaker-reference.md | @CircuitBreaker, state machine (CLOSED/OPEN/HALF_OPEN), custom predicates |
| timeout-reference.md | @Timeout patterns, per-attempt vs overall timeout, thread interruption |
| fallback-reference.md | @Fallback methods, signature rules, fallback patterns |
| resilience-config-reference.md | Full configuration reference, custom predicates, high-throughput tuning |
Supported Signatures
Java
Class must be non-final for AOP to work.
| Return Type | Example |
|---|---|
T (or Void) |
User getUser(String id) |
Optional<T> |
Optional<User> getUser(String id) |
Mono<T> / Flux<T> |
Project Reactor types (require io.projectreactor:reactor-core) |
Kotlin
Class and methods must be open for AOP to work.
| Return Type | Example |
|---|---|
T / T? / Unit |
fun getUser(id: String): User? |
suspend fun ... : T |
suspend fun getUserAsync(id: String): User (requires kotlinx-coroutines-core) |
Flow<T> |
fun getAllUsers(): Flow<User> (requires kotlinx-coroutines-core) |
Common Pitfalls
| Problem | Solution |
|---|---|
| Annotations don't trigger | Java: class must be non-final. Kotlin: class and methods must be open |
| Circuit breaker never opens | Check minimumRequiredCalls — need enough calls to evaluate |
| Circuit breaker reacts to 404 | Implement CircuitBreakerPredicate to exclude business errors |
| Fallback not called | Check method signature: return type compatible, parameters match subset |
| Retry makes latency worse | Estimate worst-case: attempts × (delay + delayStep). Reduce for latency-sensitive paths |
| Config not applied | Config key must match annotation value: @Retry("custom") → resilient.retry."custom" |
Troubleshooting
Verify AOP is working
# Check generated proxy classes
find build -name "*__AopProxy.java" | head -5
If no proxies are generated:
- Check the class is non-final (Java) /
open(Kotlin), and the method isopenin Kotlin. - Verify the annotation processor is wired: Java
annotationProcessor "ru.tinkoff.kora:annotation-processors", Kotlinksp "ru.tinkoff.kora:symbol-processors". - Clean rebuild:
./gradlew clean build --no-daemon
Enable debug logging
logging.levels {
"ru.tinkoff.kora.resilient": "DEBUG"
}
Assets
Templates in assets/:
| Template | Description |
|---|---|
ResilientService.java.template |
Full resilience stack (Java) |
ResilientService.kt.template |
Full resilience stack (Kotlin) |
RetryService.*.template |
@Retry pattern |
CircuitBreakerService.*.template |
@CircuitBreaker pattern |
TimeoutService.*.template |
@Timeout pattern |
FallbackService.*.template |
@Fallback pattern |
See assets/ for usage.
See Also
- kora-http-client — Pair resilient with outbound HTTP clients
- kora-telemetry-metrics — Monitor retry attempts, circuit breaker state
- kora-aop-logging — Add logging to resilient services