diff --git a/libraries/pom.xml b/libraries/pom.xml
index 70346fd799..a7efd55f1d 100644
--- a/libraries/pom.xml
+++ b/libraries/pom.xml
@@ -705,6 +705,37 @@
${jctools.version}
+
+
+ io.github.resilience4j
+ resilience4j-circuitbreaker
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-ratelimiter
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-bulkhead
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-retry
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-cache
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-timelimiter
+ ${resilience4j.version}
+
org.apache.commons
commons-math3
@@ -951,6 +982,7 @@
0.9.4.0006L
2.1.2
2.5.11
+ 0.12.1
3.6.1
3.5.2
3.6
diff --git a/libraries/src/test/java/com/baeldung/resilience4j/Resilience4jUnitTest.java b/libraries/src/test/java/com/baeldung/resilience4j/Resilience4jUnitTest.java
new file mode 100644
index 0000000000..ced95c99cb
--- /dev/null
+++ b/libraries/src/test/java/com/baeldung/resilience4j/Resilience4jUnitTest.java
@@ -0,0 +1,126 @@
+package com.baeldung.resilience4j;
+
+import io.github.resilience4j.bulkhead.Bulkhead;
+import io.github.resilience4j.bulkhead.BulkheadConfig;
+import io.github.resilience4j.bulkhead.BulkheadRegistry;
+import io.github.resilience4j.circuitbreaker.CircuitBreaker;
+import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
+import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryConfig;
+import io.github.resilience4j.retry.RetryRegistry;
+import io.github.resilience4j.timelimiter.TimeLimiter;
+import io.github.resilience4j.timelimiter.TimeLimiterConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.concurrent.*;
+import java.util.function.Function;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class Resilience4jUnitTest {
+
+ interface RemoteService {
+
+ int process(int i);
+ }
+
+ private RemoteService service;
+
+ @Before
+ public void setUp() {
+ service = mock(RemoteService.class);
+ }
+
+ @Test
+ public void whenCircuitBreakerIsUsed_thenItWorksAsExpected() {
+ CircuitBreakerConfig config = CircuitBreakerConfig.custom()
+ // Percentage of failures to start short-circuit
+ .failureRateThreshold(20)
+ // Min number of call attempts
+ .ringBufferSizeInClosedState(5)
+ .build();
+ CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
+ CircuitBreaker circuitBreaker = registry.circuitBreaker("my");
+ Function decorated = CircuitBreaker.decorateFunction(circuitBreaker, service::process);
+
+ when(service.process(anyInt())).thenThrow(new RuntimeException());
+
+ for (int i = 0; i < 10; i++) {
+ try {
+ decorated.apply(i);
+ } catch (Exception ignore) {
+ }
+ }
+
+ verify(service, times(5)).process(any(Integer.class));
+ }
+
+ @Test
+ public void whenBulkheadIsUsed_thenItWorksAsExpected() throws InterruptedException {
+ BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(1).build();
+ BulkheadRegistry registry = BulkheadRegistry.of(config);
+ Bulkhead bulkhead = registry.bulkhead("my");
+ Function decorated = Bulkhead.decorateFunction(bulkhead, service::process);
+
+ Future> taskInProgress = callAndBlock(decorated);
+ try {
+ assertThat(bulkhead.isCallPermitted()).isFalse();
+ } finally {
+ taskInProgress.cancel(true);
+ }
+ }
+
+ private Future> callAndBlock(Function decoratedService) throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ when(service.process(anyInt())).thenAnswer(invocation -> {
+ latch.countDown();
+ Thread.currentThread().join();
+ return null;
+ });
+
+ ForkJoinTask> result = ForkJoinPool.commonPool().submit(() -> {
+ decoratedService.apply(1);
+ });
+ latch.await();
+ return result;
+ }
+
+ @Test
+ public void whenRetryIsUsed_thenItWorksAsExpected() {
+ RetryConfig config = RetryConfig.custom().maxAttempts(2).build();
+ RetryRegistry registry = RetryRegistry.of(config);
+ Retry retry = registry.retry("my");
+ Function decorated = Retry.decorateFunction(retry, (Integer s) -> {
+ service.process(s);
+ return null;
+ });
+
+ when(service.process(anyInt())).thenThrow(new RuntimeException());
+ try {
+ decorated.apply(1);
+ fail("Expected an exception to be thrown if all retries failed");
+ } catch (Exception e) {
+ verify(service, times(2)).process(any(Integer.class));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void whenTimeLimiterIsUsed_thenItWorksAsExpected() throws Exception {
+ long ttl = 1;
+ TimeLimiterConfig config = TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(ttl)).build();
+ TimeLimiter timeLimiter = TimeLimiter.of(config);
+
+ Future futureMock = mock(Future.class);
+ Callable restrictedCall = TimeLimiter.decorateFutureSupplier(timeLimiter, () -> futureMock);
+ restrictedCall.call();
+
+ verify(futureMock).get(ttl, TimeUnit.MILLISECONDS);
+ }
+}