Registro de cambios
Cambios notables en cada versión de resilience4cata, de la más reciente a la más antigua.
El formato sigue Keep a Changelog, y el proyecto usa Versionado Semántico. Hasta 1.0.0, una versión menor puede cambiar la API.
[0.10.0] - 2026-10-27 :id=0.10.0
Agregado
@PolicyOrder: cambia el orden de las políticas de un método. Nombra capas (Layer.RETRY,TIMEOUT,CIRCUIT_BREAKER, …) que se mueven juntas, en el orden indicado, al lugar de la más exterior de ellas; las demás conservan su orden por defecto.@PolicyOrder({TIMEOUT, RETRY})pone un único timeout alrededor de todo el bucle del retry,{CIRCUIT_BREAKER, RETRY}registra un resultado por operación en el breaker,{BULKHEAD, RETRY}mantiene el slot entre reintentos. En un método, un tipo o un bundle; se decide al compilar, y el fallback sigue siendo la capa más exterior. Nombrar una capa dos veces es R4C-ORD-001. Sin ella, o cuando no cambia nada, el código generado no cambia. UnPipelinemantiene el orden por defecto. Consulta Cambiar el orden.
[0.9.0] - 2026-10-27 :id=0.9.0
Cambiado
- Los tipos de retorno reactivos son un error de compilación. Un método con anotaciones de resiliencia que devuelve un tipo reactivo perezoso (
Flow.Publishero elPublisherde Reactive Streams, es decirMonoyFluxde Reactor;UniyMultide Mutiny; los tipos de RxJava) compilaba como un método síncrono, así que las políticas solo protegían la construcción del pipeline y un fallo nunca se reintentaba ni tenía timeout. Ahora es R4C-ASY-002; devuelve unCompletionStageen su lugar (uni.subscribeAsCompletionStage(),mono.toFuture()). - Quarkus: dos casos que perdían la protección sin aviso son errores de build. Un recurso de Quarkus REST que es una clase anotada (R4C-QK-007: Quarkus REST crea las clases de recursos por su cuenta, así que la subclase generada nunca atendía los endpoints), y un bean protegido con
@LookupIfProperty/@LookupUnlessProperty(R4C-QK-006: la condición se ignoraba).
Corregido
- Quarkus: condiciones de build en clases anotadas.
@IfBuildProfile,@UnlessBuildProfile,@IfBuildPropertyy@UnlessBuildPropertyen una clase anotada se ignoraban, así que tanto la implementación de producción como la de pruebas eran beans (una inyección ambigua). Ahora la subclase generada de una clase deshabilitada también queda deshabilitada. - Anotaciones repetidas en las subclases generadas. Los overrides ya no repiten las anotaciones de Bean Validation (
@Validy las restricciones, que Bean Validation hereda del método sobrescrito): un valor de retorno con@Validimpedía arrancar una aplicación Quarkus (HV000131). En Quarkus, un@Namedde clase sin valor ahora nombra al bean como la clase anotada, no…$Resilient, y un método@Scheduledya no le da dos scopes al bean. En Micronaut, una clase marcada con@ResilientBeanque además tenía anotaciones de políticas propias aplicaba dos veces las políticas de su interfaz (un@Retry(maxAttempts = 3)hacía 9 intentos); su subclase generada ya no se vuelve a envolver. - Micronaut: subclases y
@MockBeande clases anotadas. La subclase generada llevaba@Replaces(C.class), que Micronaut aplica a todo bean asignable aC: una subclase anotada deC(con su propio@Named) desaparecía, y el@MockBeandeCen un test no dejaba ningún bean. La subclase generada ya no lo lleva; la integración deja fuera solo la definición de bean de la propiaC, y un@Inject Csin calificador sigue prefiriendoC$Resilientsobre los beans de subclases, como Micronaut prefiere el tipo exacto. El stub de un@MockBeande una clase anotada ya no se reporta como desprotegido (R4C-MN-004). Una subclase generada compilada sin-Ar4c.inject=micronautque Micronaut crea igualmente (su constructor repite@Inject) se reporta con R4C-MN-004, porque no tiene el ámbito de la clase. - Spring Boot. El endpoint de actuator no arrancaba en aplicaciones web (
Failed to extract parameter names), porque el módulo se compilaba sin-parameters. Una clase anotada con un constructor sin parámetros y otros, ninguno@Autowired, no arrancaba (No default constructor found): ahora Spring usa el espejo generado del constructor sin parámetros, como usa ese constructor en la clase. Los beans de una interfaz anotada genérica (NameLookup implements Lookup<String>) vuelven a poder inyectarse por su argumento de tipo (@Autowired Lookup<String>) una vez envueltos. - Dobles de prueba de beans protegidos. Spring: un
@MockitoSpyBeande una interfaz anotada se envolvía después de que Spring lo creara, así queverify(spy)fallaba conNotAMockException; los spies ya no se envuelven, igual que los mocks de@MockitoBean. Quarkus:@InjectMocksobre una interfaz anotada fallaba porque su envoltorio era@Singleton; ahora el envoltorio de un bean@ApplicationScopedtambién es@ApplicationScoped(detrás de un proxy de cliente). Micronaut: un@MockBeande una interfaz@ResilientBean(un@Client) hacía fallar el arranque con R4C-MN-003; ahora se inyecta tal cual. - Configuración y mensajes.
down-when-open(Spring, Micronaut y Quarkus) partía en su coma un id derivado comocom.acme.Charges#charge(java.lang.String,int), así que no correspondía a ningún breaker; ahora las partes se vuelven a unir. Micronaut con singletons anticipados hacía fallar el arranque con R4C-CFG-013 por una clave de instancia de un bean protegido@Prototype,@Refreshableo de ámbito de petición; el valor por defecto sigue siendowarncuando existen esos beans. Un valor inválido deresilience4cata.micronaut.verify-instancesahora falla con el código nuevo R4C-MN-006. El informe de arranque de Spring para R4C-CFG-013 indicaresilience4cata.spring.verify-instances=warnpara beans creados después del arranque (@Lazy, prototypes, pipelines). El mensaje de R4C-CFG-013 cierra la frase antes de las instancias conocidas y dice<interface or class>. Los enlaces de los mensajes y de los metadatos usan las URLs actuales del sitio. - Quarkus: interfaces anotadas genéricas.
@Inject Store<String>de unStore<T>anotado recibía el bean sin proteger, porque los tipos del bean wrapper eran raw; ahora conservan los argumentos de tipo del bean. - Las clases anotadas conservan los nombres de sus parámetros. Los overrides de una subclase generada llamaban a sus parámetros
p0,p1, …, así que fallaban en las clases anotadas las funciones que leen los nombres en el método invocado:@Cacheable(key = "#sku")y@EventListener(condition = "#event.urgent")de Spring,@PathVariable/@QueryValuesin nombre explícito en controladores de Spring MVC y Micronaut, y Bean Validation, que reportaba cada violación dos veces (p0y el nombre real). Ahora conservan los nombres declarados; solo se renombra un parámetro llamadojava,dev, como la primera parte del paquete de un fallback estático, o que empiece porr4c$. Un método heredado de un archivo de clase solo tiene sus nombres si esa clase se compiló con-parameters, como hacen los builds de Spring Boot, Micronaut y Quarkus. Los wrappers de interfaces no cambian.
[0.8.0] - 2026-10-27 :id=0.8.0
Agregado
resilience4cata-all: una sola dependencia para empezar. Depende decore,annotationsyconfigy no tiene código propio, así que un proyecto necesita dos líneas:resilience4cata-ally el procesador en el path de procesadores de anotaciones (ninguna dependencia puede llevar un procesador ahí). También es un módulo JPMS,dev.resilience4cata.all, que lee los tres de forma transitiva. Los módulos por separado y el BOM funcionan como antes; úsalos para dejar las anotaciones solo en compilación. Incluido en el BOM. Ver La forma corta.
[0.7.0] - 2026-10-27 :id=0.7.0
Añadido
locked = trueen@Retry,@Hedge,@CircuitBreaker,@RateLimiter,@AdaptiveLimit,@Timeouty@Bulkhead(yPipeline.Builder.locked()): los valores que fija la anotación ganan sobre los overrides por instancia (instances.<id>.*,retryInstance(...)y similares); los atributos que deja sin fijar se pueden seguir sobrescribiendo. Un override ignorado se escribe en el log al crear la instancia, lo lista el nuevoregistry.lockedOverrides()(LockedOverride), yconfig.verifyInstances(registry)lo reporta clave por clave con el nuevo código R4C-CFG-014, así que las integraciones de Spring, Micronaut y Quarkus lo reportan al arrancar. El código generado solo cambia en las anotaciones conlocked = true. Ver Bloquear una instancia.- El build compara la API pública de cada módulo publicado con la última versión (
./gradlew apiCompatibility, parte decheck), con japicmp.
[0.6.0] - 2026-10-26 :id=0.6.0
Agregado
@AdaptiveLimit: límites de concurrencia adaptativos, sync y async, en interfaces, clases anotadas yPipeline.adaptiveLimit(...). Como un bulkhead, limita las llamadas que corren al mismo tiempo, pero mueve el límite por sí mismo, entreminLimitymaxLimit, según la latencia de las llamadas:GRADIENT2(el valor por defecto) compara la latencia promedio de cada ventana con un promedio de largo plazo,AIMDcrece de a uno y retrocede porbackoffRatiodespués de un descarte; los dos siguen concurrency-limits de Netflix. Un fallo dedropOn(por defecto unaCallTimeoutException, unaBulkheadFullExceptiono unaTimeoutExceptionde las políticas de adentro) cuenta como descarte; los demás fallos no son muestras. Un límite lleno rechaza enseguida con la nuevaLimitExceededException. El límite se recalcula una vez por ventana (window, al menosminWindowSamplesllamadas) en el hilo que termina una llamada después de que la ventana terminó: sin hilo de fondo, sin asignaciones en el camino de éxito. Está entre el rate limiter y el timeout:Fallback → Retry | Hedge → CircuitBreaker → RateLimiter → AdaptiveLimit → Timeout → Bulkhead. API nueva:AdaptiveLimitConfig(con el ajuste de los algoritmos:smoothing,rttTolerance,longWindow,queueSize,backoffRatio),AdaptiveLimitHandledesderegistry.adaptiveLimit(id),AdaptiveLimitSnapshotenregistry.snapshot().adaptiveLimits(),AdaptiveLimitEvent.RejectedyLimitChanged,adaptiveLimitDefaults,adaptiveLimit,adaptiveLimitOverlayyadaptiveLimitInstanceen el builder del registry, claves de configuración externa*.adaptiveLimit.*, y las métricasr4c.adaptivelimit.limit,r4c.adaptivelimit.concurrent.calls,r4c.adaptivelimit.limit.changesyr4c.calls.rejected{kind=adaptivelimit}en los dos módulos de métricas. Nuevo error del procesador R4C-ATR-009. Consulta Adaptive limit.@Hedgedeja de lanzar intentos cuando un adaptive limit rechaza uno, como con un breaker abierto o un bulkhead lleno.- Tarjeta en la Dev UI de Quarkus: en modo dev,
/q/dev-uimuestra cada instancia de política del registry (breakers, bulkheads, rate limiters, adaptive limits, retries y hedges con sus presupuestos, timeouts), actualizada cada dos segundos, y puede forzar la apertura de un circuit breaker, deshabilitarlo o reiniciarlo. Solo en modo dev: la tarjeta y su servicio JsonRPC no existen en builds de tests ni de producción. Ver Quarkus, Dev UI.
Corregido
- El endpoint
resilience4catadel actuator de Spring Boot ahora incluye los hedges cuando reporta una sola instancia (/actuator/resilience4cata/{instance}).
[0.5.0] - 2026-10-26 :id=0.5.0
Agregado
resilience4cata-micronaut: integración con Micronaut 5 (Micronaut 5.2 o posterior, Java 25, sin AOP). Los beans de interfaces anotadas marcados con@ResilientBean(I.class)se reemplazan por su envoltorio generado cuando Micronaut los crea (expuestos solo comoI; la AOP de Micronaut sobre el bean se ejecuta dentro de las políticas); las clases anotadas las crea Micronaut como su subclase generada con la nueva opción del procesador de abajo, o se devuelven desde un@Factory; unResilienceConfigy unResilienceRegistry@Contextconfigurados desde la configuraciónresilience4cata.*de la aplicación con la precedencia de Micronaut (listas YAML, ids de instancia derivados entre comillas, el archivo de cada clave incorrecta), con personalización medianteBeanCreatedEventListener<ResilienceRegistry.Builder>y beansEventListener/Ticker; métricas de Micrometer cuando estánresilience4cata-micrometery unMeterRegistry; un indicador de saludresilience4cata(UP con breakers abiertos salvo los listados); un endpoint de gestiónresilience4catade solo lectura y sensible. Nueva área de erroresR4C-MN-001…005: un@ResilientBeanmal usado detiene el arranque;R4C-MN-004reporta una clase anotada creada sin su subclase generada;R4C-MN-005detiene un método@Transactionalreintentado o con hedge de una clase anotada (todos los intentos compartirían una transacción), salvo conresilience4cata.micronaut.allow-retry-inside-transaction=true. El módulo se compila con--release 25, la única excepción a la base Java 21. Consulta Micronaut.- Opción del procesador
-Ar4c.inject=micronaut: la subclase generada de una clase anotada repite las anotaciones a nivel de tipo de la clase (ámbito, calificadores,@Requires…) y añade@Replaces(C.class), para que Micronaut la cree en lugar de la clase. Sin la opción el código generado no cambia. Un valor distinto demicronaut, o la opción sin Micronaut en el class path de compilación, es el nuevo error R4C-APT-006. resilience4cata-quarkus: extensión de Quarkus (Quarkus 3.40 o posterior; ejecuciónresilience4cata-quarkusy despliegueresilience4cata-quarkus-deployment, ambos en el BOM; sin interceptores). Al construir, una clase anotada se veta y su subclase generada pasa a ser el bean con el ámbito, los qualifiers y los estereotipos de la clase (las llamadas sobrethisse interceptan); un bean de una interfaz anotada se inyecta a través de un bean envoltorio sintético (Resilient.of, una alternativa habilitada con los qualifiers del bean;@Injectde la clase sigue dando el bean); una interfaz de cliente REST (@RegisterRestClient) con envoltorio recibe un bean@Defaultalrededor del@RestClient. Un beanResilienceRegistry(@DefaultBean) se configura desde las clavesresilience4cata.*de la aplicación a través de SmallRye Config (perfiles de Quarkus, expresiones, variablesR4C_, fichero y línea de cada clave incorrecta), y se crea al arrancar para que los errores de configuración lo detengan; se aplican los beansResilienceRegistryCustomizer,EventListeneryTicker; las clavesresilience4cata.instances.<id>se verifican al arrancar (quarkus.resilience4cata.verify-instances,warnpor defecto). Métricas de Micrometer conquarkus-micrometeryresilience4cata-micrometer(si no, OpenTelemetry), una comprobación de preparaciónresilience4cata(UP con breakers abiertos salvo los listados enquarkus.resilience4cata.health.down-when-open). Nueva área de erroresR4C-QK-001…005, reportados mientras Quarkus construye la aplicación: R4C-QK-001 (anotaciones de MicroProfile/SmallRye Fault Tolerance y de resilience4cata en un mismo método), R4C-QK-002 (un método@Transactionalreintentado o con hedge de una clase anotada;quarkus.resilience4cata.allow-retry-inside-transaction), R4C-QK-003 (varias interfaces anotadas no relacionadas), R4C-QK-004 (productores en una clase anotada), R4C-QK-005 (una clase anotada de ámbito normal sin constructor sin parámetros). Consulta Quarkus, incluida la comparación con SmallRye Fault Tolerance. Se construyó a mano una imagen nativa de la aplicación de la guía (GraalVM para JDK 25.0.4, Quarkus 3.40.1, macOS arm64), que sirvió la clase anotada, el envoltorio de la interfaz, el del cliente REST, la comprobación de preparación y las métricas sin configuración nativa adicional; las construcciones nativas no forman parte de la CI. En una imagen nativa, los errores de configuración nombran la fuente que informa SmallRye allí (Runtime Valuespara los valores fijados al construir) en lugar del fichero y la línea.
Corregido
- Con un ticker manual (
ManualClock), un retry asincrónico cuyo siguiente delay es cero (delay = 0, o un jitterFULLque sortea cero) lanzaba el siguiente intento en un hilo virtual nuevo en lugar de en el reloj, así que los tests competían con él. Ahora todo delay que una política programa en un ticker pasa porTicker.schedule, también el cero, y se ejecuta en el hilo que avanza el reloj la próxima vez (también conadvance(Duration.ZERO)), como ya hacían los hedges tras una falla. Sin ticker no cambia nada.Ticker.schedulepuede recibir ahora un delay de cero. - R4C-RT-003 en una imagen nativa de GraalVM ahora también señala los metadatos de alcanzabilidad que escribe el procesador (
META-INF/native-image/dev.resilience4cata.generated/<clase>/reachability-metadata.json), que deben estar en el class path del build de la imagen. En la JVM el mensaje no cambia.
[0.4.0] - 2026-10-25 :id=0.4.0
Agregado
@Hedge: peticiones de cobertura (hedged requests) para métodos asincrónicos yPipeline.executeAsync. La misma llamada se vuelve a lanzar cuando llevadelaysin resultado (1 s por defecto), o enseguida cuando falla con una falla dehedgeOn; gana el primer éxito y los demás intentos se cancelan a través de cada política interna (el breaker no registra nada, el bulkhead libera el slot).maxAttempts(de 2 a 10, 2 por defecto),abortOn, y un presupuesto de hedge activo por defecto (budgetPercent10,budgetMinHedgesPerSecond1,budgetWindow10 s). Ocupa el lugar de Retry en el orden, y no se puede combinar con@Retry. NuevosHedgeConfig,HedgeCancelledException,HedgeEvent(Hedged,Won,Skipped,Failed),HedgeSnapshotenResilienceSnapshot.hedges(),ResilienceRegistry.Builder.hedgeDefaults/hedge/hedgeOverlay/hedgeInstance,Pipeline.Builder.hedge(...), y las claves de configdefaults.hedge.*(yconfigs.…,instances.…, variables de entorno). Solo para llamadas idempotentes. Nuevos errores R4C-HDG-001 (método sincrónico), R4C-HDG-002 (con@Retry), R4C-ATR-008 (maxAttempts), R4C-RT-018 (Pipeline.executecon un hedge) y R4C-RT-019 (retry y hedge en un mismo pipeline); R4C-ATR-007 cubre ahora también el presupuesto de hedge. Ver Hedge.resilience4cata-spring: integración con Spring Boot 4 (Spring Boot 4.1 o posterior, un solo módulo, sin AOP). Las clases anotadas se instancian como su subclase generada en lugar de la clase (tipo del bean, nombre, qualifiers e inyección por constructor sin cambios; las llamadas sobrethisse interceptan); los beans de interfaces anotadas se envuelven conResilient.ofdespués de todos los demás post-procesadores; un beanResilienceRegistry(@ConditionalOnMissingBean) se configura desde las propiedadesresilience4cata.*de la aplicación con la precedencia de Spring, con ids de instancia entre[…]y listas YAML, validado al arrancar con el fichero y la línea de cada clave incorrecta; se aplican los beansResilienceRegistryCustomizer,EventListeneryTicker; métricas de Micrometer cuando estáresilience4cata-micrometer; un indicador de saludresilience4cata(UP con breakers abiertos salvo los listados); un endpoint de actuatorresilience4catade solo lectura; metadatos para el IDE; análisis de fallos; funciona con Spring AOT. Nueva área de erroresR4C-SPR-001…004;R4C-SPR-004detiene el arranque cuando un método reintentado o con hedge de una clase anotada también es@Transactional(todos los intentos compartirían una transacción), salvo conresilience4cata.spring.allow-retry-inside-transaction=true. Consulta Spring Boot.resilience4cata-config:ResilienceConfig.Builder.precedenceByOrder()(las fuentes se ordenan por el orden en que se agregan, para frameworks con su propia precedencia) yproperties(description, map, origins)(dónde se fijó cada clave, para los mensajes).- Imagen nativa de GraalVM sin configuración. El procesador escribe
META-INF/native-image/dev.resilience4cata.generated/<clase generada>/reachability-metadata.jsonpara cada clase generada (un archivo por clase, así que el procesador sigue siendo isolating), que registra sus constructores públicos paraResilient.ofyResilient.create, condicionado a la interfaz anotada (o aResilientpara las clases anotadas).coretrae unnative-image.propertiesque inicializa en tiempo de ejecución los holders de sus hilos de fondo. Requiere GraalVM for JDK 23 o posterior; ver Imagen nativa. - Clases anotadas: los constructores generados conservan los nombres de parámetro de los constructores de la clase y copian las anotaciones del constructor y de los parámetros (retención
CLASSoRUNTIME), así que los contenedores de inyección de dependencias ven@Inject/@Autowired,@Qualifier,@Named,@Value… también enC$Resilient.@ConstructorPropertiesrecibe primero el nombre del registry. Los overrides generados repiten las anotaciones de los métodos que sobrescriben y de sus parámetros (salvo las anotaciones propias de resilience4cata y los bundles), así que las anotaciones que se leen solo en el método invocado, como el@Transactionalde Jakarta en Spring o los interceptor bindings, ya no se pierden. Ver Constructores e inyección de dependencias.
Cambiado
ResilienceEventadmite un nuevo subtipo sellado,HedgeEvent: unswitchsobreResilienceEventsin ramadefaultdeja de compilar hasta que manejeHedgeEvent(o añadadefault).ResilienceSnapshottiene un componente nuevo,hedges(); su constructor de cinco listas se conserva. A partir de ahora la jerarquía crece en versiones menores: mantén una ramadefault, o escucha tipos de record específicos (Observabilidad).- La subclase generada para una clase anotada ya no es
final(public class C$Resilient extends C), así que los frameworks pueden crear proxies de ella (CGLIB para@Transactional, proxies con scope). Los envoltorios de interfaces siguen siendofinal. - Los constructores generados de las clases anotadas nombran sus parámetros como el constructor de la clase (
registry, warehouseen vez deregistry, p0); un parámetro llamadoregistryhace que el del registry sear4c$registry, y los parámetros llamadosjavaodevpasan ar4c$p<índice>. Las llamadas no cambian.
Corregido
- Cancelar una llamada async ahora cancela el trabajo que hay dentro. Cancelar el future que devuelve un método async generado o
Pipeline.executeAsync(o completarlo con unaCancellationException) se detenía en la política más externa: el stage interno seguía corriendo, un slot del bulkhead quedaba ocupado hasta que ese stage terminara, un retry podía empezar otro intento, un fallback podía correr, y el circuit breaker podía registrar la cancelación como una falla. Ahora la cancelación baja por todas las capas hasta el stage del método: el retry cancela su backoff y no empieza más intentos, el timeout cancela su plazo, el bulkhead libera su slot en cuanto termina el stage (de inmediato para unCompletableFuture) y una llamada en cola sale de la cola, el breaker no registra nada (un permiso de half-open se devuelve), el fallback no corre, y una llamada que todavía espera un permiso del rate limiter lo devuelve y nunca empieza. Una cancelación no publica eventos. Un timeout que vence cancela las capas internas de la misma forma, así que un bulkhead dentro de un timeout libera su slot cuando la llamada vence. Cada política crea por llamada la misma cantidad de objetos que antes. Ver Métodos async.
[0.3.0] - 2026-10-25 :id=0.3.0
Agregado
resilience4cata-opentelemetry: métricas de OpenTelemetry.OpenTelemetryMetrics.create(openTelemetry),register(builder)ybindTo(registry)reportan rechazos, retries, abandonos, transiciones del breaker, timeouts y fallbacks como contadores alimentados por eventos, y el estado de breakers, bulkheads y rate limiters como gauges asíncronos leídos del snapshot en cada recolección, así que se incluyen las instancias creadas después. Los mismos nombres de métricas que el módulo de Micrometer. Solo métricas, todavía sin tracing. Consulta OpenTelemetry.resilience4cata-junit: una extensión de JUnit Jupiter.@ResilienceTestinyecta en cada prueba (y en sus métodos@BeforeEach/@AfterEach) unManualClocknuevo y unResilienceRegistryque lo sigue, conseed,startNanosy unfailOnPendingTasksopcional; los métodos@ConfigureRegistrypersonalizan el builder. Necesita JUnit 5.10 o posterior, JUnit 6 incluido. Consulta Pruebas.- Una llamada async con
@Timeoutque termina a tiempo ahora quita su plazo de unManualClockde inmediato, así quependingTasks()ya no la cuenta hasta que pasa el plazo. resilience4cata-micrometer: métricas de Micrometer en tres líneas (MicrometerMetrics.create(meterRegistry),register(builder),bindTo(registry)): counters a partir de eventos (rechazos, transiciones del breaker, reintentos, abandonos, timeouts, fallbacks) y gauges para cada circuit breaker, bulkhead y rate limiter, incluidas las instancias creadas después de conectar. Reemplaza la receta escrita a mano de la guía de observabilidad.ResilienceRegistry.onInstanceCreated(Runnable): ejecuta una acción una vez por cada instancia de política nueva (al envolver un servicio o construir un pipeline, nunca durante una llamada), para que los puentes de métricas registren meters para instancias creadas después (Observabilidad).- Presupuesto de reintentos para
@Retry,RetryConfigyPipeline:budgetPercent,budgetMinRetriesPerSecondybudgetWindowlimitan los reintentos de una instancia de retry, compartidos por todos sus llamadores, a una reserva por segundo más un porcentaje de sus llamadas en la ventana, para que una caída no multiplique la carga sobre la dependencia que falla.budgetPercentlo activa, con una reserva por defecto de 10 reintentos por segundo. Apagado por defecto; sin él, el código generado y el camino de éxito no cambian. Un reintento rechazado publicaRetryEvent.GaveUpcon la nueva razónBUDGET_EXHAUSTED;RetrySnapshot.budget()muestra la ventana. Clavesdefaults.retry.budget…(yconfigs.…,instances.…, variables de entorno); nuevo error del procesador R4C-ATR-007. Presupuesto de reintentos - Códigos de error para los módulos de pruebas:
R4C-TK-001…003(ManualClock,ResilienceTesting) yR4C-JU-001…003(la extensión de JUnit), cada uno documentado en la referencia de errores con su corrección. - Los módulos de Micrometer y OpenTelemetry dan a cada métrica la misma descripción (el texto
# HELPde Prometheus), comprobado por un test.
[0.2.0] - 2026-10-25 :id=0.2.0
Corrige tres errores de comportamiento de la 0.1.0; uno de los arreglos cambia un valor por defecto (ver Actualizar).
Actualizar desde 0.1.0
- Mantén
coreyprocessoren la misma versión (importa el BOM). El código generado por el procesador 0.2.0 necesita elcore0.2.0; el código generado por la 0.1.0 sigue enlazando contra elcore0.2.0, que solo agrega API. abortOnya no contieneCallNotPermittedExceptionpor defecto, porque ahora un circuit breaker abierto detiene los reintentos por sí solo. QuitaCallNotPermittedExceptionde tus propias listasabortOnsi lo agregaste solo para eso; dejarlo no causa problemas.- Una llamada rechazada por un breaker abierto ya no se puede reintentar con ninguna configuración, ni siquiera con un classifier.
Cambiado
- Un circuit breaker abierto siempre detiene los reintentos. Una llamada rechazada con
CallNotPermittedExceptionnunca se reintenta, digan lo que diganabortOn,retryOno un classifier, en el código generado (sincrónico y async) y enPipeline. En 0.1.0 esto venía delabortOnpor defecto, así que definir tu propioabortOnlo quitaba sin aviso y el retry seguía llamando a un breaker abierto.RetryConfig.DEFAULTS.abortOn()(ydefaults.retry.abortOn) ahora está vacío; Retry.
Corregido
FallbackEvent.Appliedya no se publica para una falla que ningún método hermano de fallback recibe (por ejemplo unErrorcuando el único hermano recibeException): esa falla se relanza, y el evento ahora significa que un fallback realmente se ejecutó.- Los mensajes de error R4C-ATR-001 y R4C-CFG-002 indican la gramática de duraciones una vez en lugar de dos, y R4C-CFG-007 cierra la oración antes de "Known configs".
- El descriptor de módulo del procesador declara también
AnnotationClaimer, así que-Xlint:processingno avisa cuando el procesador corre desde el module path de procesadores junto a otros procesadores. - Un classifier que lanza dentro de
@CircuitBreaker(o del breaker de unPipeline) ahora termina la llamada enseguida, sync y async: el retry que lo rodea ya no clasifica la excepción del classifier como la falla del método ni la reintenta (lo que podía abrir el breaker y mostrarCallNotPermittedExceptionen su lugar). El breaker registra una falla, el retry publicaGaveUp(CLASSIFIER_FAILED)una vez, y el fallback o quien llama recibe la excepción del classifier. Ver Un classifier que lanza.
0.1.0 - 2026-10-25 :id=0.1.0
Primera versión. Artefactos en Maven Central bajo el groupId dev.resilience4cata: resilience4cata-annotations, -core, -processor, -config, -testkit y -bom. Requiere Java 21 o posterior; probado en Java 21 y 25.
Agregado
- Seis políticas como anotaciones, aplicadas en un orden fijo (Fallback → Retry → CircuitBreaker → RateLimiter → Timeout → Bulkhead → método):
@Retry(backoff fijo o exponencial, jitter, presupuesto de tiempo),@CircuitBreaker(ventana por cantidad o por tiempo, llamadas lentas, sondeos en semiabierto),@RateLimiter(GCRA, espera opcional),@Timeout(en el thread de quien llama, basado en interrupciones),@Bulkhead(semáforo, espera opcional) y@Fallback(método hermano o clase handler). - Procesador de anotaciones que genera una clase Java pura por cada tipo anotado:
<Interface>$Resilient, que envuelve una implementación (Resilient.of), o<Class>$Resilient, que extiende una clase anotada (Resilient.create). Las anotaciones y sus valores se verifican al compilar; procesamiento incremental isolating en Gradle; funciona con-Xlint:all -Werror. - Métodos async que devuelven
CompletionStage<T>oCompletableFuture<T>(también? extends T), con formas no bloqueantes de cada política (Métodos async). - Anotaciones compuestas con
@ResiliencePolicy, y anotaciones a nivel de tipo. - Classifiers que deciden a partir de un resultado o una excepción si una llamada falló, y
@Retry(onExhaustedResult = THROW). Pipeline, una API programática con las mismas políticas y el mismo orden para código que no puedes anotar.ResilienceRegistry: valores predeterminados, configs con nombre, sobrescrituras por instancia, instancias compartidas porname, handles para circuit breakers (forceOpen,disable,reset), rate limiters y bulkheads.- Eventos y snapshots: listeners registrados en el builder del registry, snapshots de solo lectura de cada instancia.
resilience4cata-config: configuración externa desde archivos properties, recursos del classpath, variables de entorno y propiedades del sistema, con perfiles,extends, sobrescrituras por instancia, validación estricta yverifyInstances(claves).resilience4cata-testkit:ManualClockyResilienceTestingpara pruebas deterministas del comportamiento basado en tiempo.resilience4cata-bompara alinear las versiones de todos los módulos.- Códigos de error (
R4C-…) en cada error de compilación y de ejecución, cada uno documentado con su solución. - Control de asignación de memoria en CI: cada ruta síncrona de éxito y de rechazo asigna 0 bytes (Rendimiento).
Limitaciones conocidas
- No hay módulos de integración con Spring, CDI, Micrometer ni OpenTelemetry; no hay configuración en YAML.
- El orden de las políticas no se puede cambiar, y la configuración no se puede cambiar después de construir el registry.
- En el estilo de interfaz, las llamadas que una implementación se hace a sí misma no se interceptan.
- Solo se admiten
CompletionStage<T>yCompletableFuture<T>como tipos async (no? super T, no tipos reactivos).